| using System; | |
| using System.Collections.Generic; | |
| using System.IO; | |
| using System.Xml; | |
| namespace Google.ProtocolBuffers.Serialization | |
| { | |
| /// <summary> | |
| /// JsonFormatReader is used to parse Json into a message or an array of messages | |
| /// </summary> | |
| public class JsonFormatReader : AbstractTextReader | |
| { | |
| private readonly JsonCursor _input; | |
| // The expected token that ends the current item, either ']' or '}' | |
| private readonly Stack<int> _stopChar; | |
| private enum ReaderState | |
| { | |
| Start, | |
| BeginValue, | |
| EndValue, | |
| BeginObject, | |
| BeginArray | |
| } | |
| private string _current; | |
| private ReaderState _state; | |
| /// <summary> | |
| /// Constructs a JsonFormatReader to parse Json into a message, this method does not use text encoding, all bytes MUST | |
| /// represent ASCII character values. | |
| /// </summary> | |
| public static JsonFormatReader CreateInstance(Stream stream) | |
| { | |
| return new JsonFormatReader(JsonCursor.CreateInstance(stream)); | |
| } | |
| /// <summary> | |
| /// Constructs a JsonFormatReader to parse Json into a message, this method does not use text encoding, all bytes MUST | |
| /// represent ASCII character values. | |
| /// </summary> | |
| public static JsonFormatReader CreateInstance(byte[] bytes) | |
| { | |
| return new JsonFormatReader(JsonCursor.CreateInstance(bytes)); | |
| } | |
| /// <summary> | |
| /// Constructs a JsonFormatReader to parse Json into a message | |
| /// </summary> | |
| public static JsonFormatReader CreateInstance(string jsonText) | |
| { | |
| return new JsonFormatReader(JsonCursor.CreateInstance(jsonText)); | |
| } | |
| /// <summary> | |
| /// Constructs a JsonFormatReader to parse Json into a message | |
| /// </summary> | |
| public static JsonFormatReader CreateInstance(TextReader input) | |
| { | |
| return new JsonFormatReader(JsonCursor.CreateInstance(input)); | |
| } | |
| /// <summary> | |
| /// Constructs a JsonFormatReader to parse Json into a message | |
| /// </summary> | |
| internal JsonFormatReader(JsonCursor input) | |
| { | |
| _input = input; | |
| _stopChar = new Stack<int>(); | |
| _stopChar.Push(-1); | |
| _state = ReaderState.Start; | |
| } | |
| /// <summary> | |
| /// Constructs a JsonFormatReader to parse Json into a message | |
| /// </summary> | |
| protected JsonFormatReader(TextReader input) | |
| : this(JsonCursor.CreateInstance(input)) | |
| { | |
| } | |
| /// <summary> | |
| /// Returns true if the reader is currently on an array element | |
| /// </summary> | |
| public bool IsArrayMessage | |
| { | |
| get { return _input.NextChar == '['; } | |
| } | |
| /// <summary> | |
| /// Returns an enumerator that is used to cursor over an array of messages | |
| /// </summary> | |
| /// <remarks> | |
| /// This is generally used when receiving an array of messages rather than a single root message | |
| /// </remarks> | |
| public IEnumerable<JsonFormatReader> EnumerateArray() | |
| { | |
| foreach (string ignored in ForeachArrayItem(_current)) | |
| { | |
| yield return this; | |
| } | |
| } | |
| /// <summary> | |
| /// Reads the root-message preamble specific to this formatter | |
| /// </summary> | |
| public override void ReadMessageStart() | |
| { | |
| _input.Consume('{'); | |
| _stopChar.Push('}'); | |
| _state = ReaderState.BeginObject; | |
| } | |
| /// <summary> | |
| /// Reads the root-message close specific to this formatter | |
| /// </summary> | |
| public override void ReadMessageEnd() | |
| { | |
| _input.Consume((char)_stopChar.Pop()); | |
| _state = ReaderState.EndValue; | |
| } | |
| /// <summary> | |
| /// Merges the contents of stream into the provided message builder | |
| /// </summary> | |
| public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry) | |
| { | |
| ReadMessageStart(); | |
| builder.WeakMergeFrom(this, registry); | |
| ReadMessageEnd(); | |
| return builder; | |
| } | |
| /// <summary> | |
| /// Causes the reader to skip past this field | |
| /// </summary> | |
| protected override void Skip() | |
| { | |
| object temp; | |
| _input.ReadVariant(out temp); | |
| _state = ReaderState.EndValue; | |
| } | |
| /// <summary> | |
| /// Peeks at the next field in the input stream and returns what information is available. | |
| /// </summary> | |
| /// <remarks> | |
| /// 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. | |
| /// </remarks> | |
| 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; | |
| } | |
| /// <summary> | |
| /// Returns true if it was able to read a String from the input | |
| /// </summary> | |
| 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; | |
| } | |
| /// <summary> | |
| /// Returns true if it was able to read a ByteString from the input | |
| /// </summary> | |
| protected override bool Read(ref ByteString value) | |
| { | |
| string bytes = null; | |
| if (Read(ref bytes)) | |
| { | |
| value = ByteString.FromBase64(bytes); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// <summary> | |
| /// Cursors through the array elements and stops at the end of the array | |
| /// </summary> | |
| protected override IEnumerable<string> 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; | |
| } | |
| /// <summary> | |
| /// Merges the input stream into the provided IBuilderLite | |
| /// </summary> | |
| protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry) | |
| { | |
| Merge(builder, registry); | |
| return true; | |
| } | |
| } | |
| } |