aboutsummaryrefslogblamecommitdiff
path: root/src/ProtocolBuffers/Serialization/JsonFormatReader.cs
blob: 2808ff5cad0b841df5a0808f45d92a4d666cc09f (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                                                                                      
                                            






                                                                                  









                                                                                                                                         

                                                                       
                                                                                                                                             


                                                                       





                                                                                                                                           
          
                            





                                          






                                                                       











































































                                                                                                         
                                                                   

                                           

                                                                                                                                                       
                              

                                                                   


                                                         
                                                                     



















































                                                                                              
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;
        private readonly Stack<int> _stopChar;

        enum ReaderState { Start, BeginValue, EndValue, BeginObject, BeginArray }
        string _current;
        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>
        /// Merges the contents of stream into the provided message builder
        /// </summary>
        public override TBuilder Merge<TBuilder>(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;
        }

        /// <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;
        }

    }
}