using System; using System.Collections.Generic; using System.IO; using System.Xml; namespace Google.ProtocolBuffers.Serialization { /// /// JsonFormatReader is used to parse Json into a message or an array of messages /// public class JsonFormatReader : AbstractTextReader { private readonly JsonCursor _input; // The expected token that ends the current item, either ']' or '}' private readonly Stack _stopChar; private enum ReaderState { Start, BeginValue, EndValue, BeginObject, BeginArray } private string _current; private ReaderState _state; /// /// Constructs a JsonFormatReader to parse Json into a message, this method does not use text encoding, all bytes MUST /// represent ASCII character values. /// public static JsonFormatReader CreateInstance(Stream stream) { return new JsonFormatReader(JsonCursor.CreateInstance(stream)); } /// /// Constructs a JsonFormatReader to parse Json into a message, this method does not use text encoding, all bytes MUST /// represent ASCII character values. /// public static JsonFormatReader CreateInstance(byte[] bytes) { return new JsonFormatReader(JsonCursor.CreateInstance(bytes)); } /// /// Constructs a JsonFormatReader to parse Json into a message /// public static JsonFormatReader CreateInstance(string jsonText) { return new JsonFormatReader(JsonCursor.CreateInstance(jsonText)); } /// /// Constructs a JsonFormatReader to parse Json into a message /// public static JsonFormatReader CreateInstance(TextReader input) { return new JsonFormatReader(JsonCursor.CreateInstance(input)); } /// /// Constructs a JsonFormatReader to parse Json into a message /// internal JsonFormatReader(JsonCursor input) { _input = input; _stopChar = new Stack(); _stopChar.Push(-1); _state = ReaderState.Start; } /// /// Constructs a JsonFormatReader to parse Json into a message /// protected JsonFormatReader(TextReader input) : this(JsonCursor.CreateInstance(input)) { } /// /// Returns true if the reader is currently on an array element /// public bool IsArrayMessage { get { return _input.NextChar == '['; } } /// /// Returns an enumerator that is used to cursor over an array of messages /// /// /// This is generally used when receiving an array of messages rather than a single root message /// public IEnumerable EnumerateArray() { foreach (string ignored in ForeachArrayItem(_current)) { yield return this; } } /// /// Reads the root-message preamble specific to this formatter /// public override void ReadMessageStart() { _input.Consume('{'); _stopChar.Push('}'); _state = ReaderState.BeginObject; } /// /// Reads the root-message close specific to this formatter /// public override void ReadMessageEnd() { _input.Consume((char)_stopChar.Pop()); _state = ReaderState.EndValue; } /// /// Merges the contents of stream into the provided message builder /// public override TBuilder Merge(TBuilder builder, ExtensionRegistry registry) { ReadMessageStart(); builder.WeakMergeFrom(this, registry); ReadMessageEnd(); return builder; } /// /// Causes the reader to skip past this field /// protected override void Skip() { object temp; _input.ReadVariant(out temp); _state = ReaderState.EndValue; } /// /// Peeks at the next field in the input stream and returns what information is available. /// /// /// This may be called multiple times without actually reading the field. Only after the field /// is either read, or skipped, should PeekNext return a different value. /// protected override bool PeekNext(out string field) { field = _current; if (_state == ReaderState.BeginValue) { return true; } int next = _input.NextChar; if (next == _stopChar.Peek()) { return false; } _input.Assert(next != -1, "Unexpected end of file."); //not sure about this yet, it will allow {, "a":true } if (_state == ReaderState.EndValue && !_input.TryConsume(',')) { return false; } field = _current = _input.ReadString(); _input.Consume(':'); _state = ReaderState.BeginValue; return true; } /// /// Returns true if it was able to read a String from the input /// protected override bool ReadAsText(ref string value, Type typeInfo) { object temp; JsonCursor.JsType type = _input.ReadVariant(out temp); _state = ReaderState.EndValue; _input.Assert(type != JsonCursor.JsType.Array && type != JsonCursor.JsType.Object, "Encountered {0} while expecting {1}", type, typeInfo); if (type == JsonCursor.JsType.Null) { return false; } if (type == JsonCursor.JsType.True) { value = "1"; } else if (type == JsonCursor.JsType.False) { value = "0"; } else { value = temp as string; } //exponent representation of integer number: if (value != null && type == JsonCursor.JsType.Number && (typeInfo != typeof(double) && typeInfo != typeof(float)) && value.IndexOf("e", StringComparison.OrdinalIgnoreCase) > 0) { value = XmlConvert.ToString((long) Math.Round(XmlConvert.ToDouble(value), 0)); } return value != null; } /// /// Returns true if it was able to read a ByteString from the input /// protected override bool Read(ref ByteString value) { string bytes = null; if (Read(ref bytes)) { value = ByteString.FromBase64(bytes); return true; } return false; } /// /// Cursors through the array elements and stops at the end of the array /// protected override IEnumerable ForeachArrayItem(string field) { _input.Consume('['); _stopChar.Push(']'); _state = ReaderState.BeginArray; while (_input.NextChar != ']') { _current = field; yield return field; if (!_input.TryConsume(',')) { break; } } _input.Consume((char) _stopChar.Pop()); _state = ReaderState.EndValue; } /// /// Merges the input stream into the provided IBuilderLite /// protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry) { Merge(builder, registry); return true; } } }