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 JsonTextCursor _input; private readonly Stack _stopChar; enum ReaderState { Start, BeginValue, EndValue, BeginObject, BeginArray } string _current; ReaderState _state; /// /// Constructs a JsonFormatReader to parse Json into a message /// public JsonFormatReader(string jsonText) { _input = new JsonTextCursor(jsonText.ToCharArray()); _stopChar = new Stack(); _stopChar.Push(-1); _state = ReaderState.Start; } /// /// Constructs a JsonFormatReader to parse Json into a message /// public JsonFormatReader(TextReader input) { _input = new JsonTextCursor(input); _stopChar = new Stack(); _stopChar.Push(-1); _state = ReaderState.Start; } /// /// 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; } /// /// Merges the contents of stream into the provided message builder /// public override TBuilder Merge(TBuilder builder, ExtensionRegistry registry) { _input.Consume('{'); _stopChar.Push('}'); _state = ReaderState.BeginObject; builder.WeakMergeFrom(this, registry); _input.Consume((char)_stopChar.Pop()); _state = ReaderState.EndValue; 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; JsonTextCursor.JsType type = _input.ReadVariant(out temp); _state = ReaderState.EndValue; _input.Assert(type != JsonTextCursor.JsType.Array && type != JsonTextCursor.JsType.Object, "Encountered {0} while expecting {1}", type, typeInfo); if (type == JsonTextCursor.JsType.Null) return false; if (type == JsonTextCursor.JsType.True) value = "1"; else if (type == JsonTextCursor.JsType.False) value = "0"; else value = temp as string; //exponent representation of integer number: if (value != null && type == JsonTextCursor.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; } } }