JsonFormatReader.cs 8.53 KB
Newer Older
1
using System;
2 3 4 5 6 7 8 9 10 11 12
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
    {
13
        private readonly JsonCursor _input;
14
        // The expected token that ends the current item, either ']' or '}'
15 16
        private readonly Stack<int> _stopChar;

17 18 19 20 21 22 23 24 25 26 27
        private enum ReaderState
        {
            Start,
            BeginValue,
            EndValue,
            BeginObject,
            BeginArray
        }

        private string _current;
        private ReaderState _state;
28

29 30 31 32
        /// <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>
33 34 35 36 37
        public static JsonFormatReader CreateInstance(Stream stream)
        {
            return new JsonFormatReader(JsonCursor.CreateInstance(stream));
        }

38 39 40 41
        /// <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>
42 43 44 45 46
        public static JsonFormatReader CreateInstance(byte[] bytes)
        {
            return new JsonFormatReader(JsonCursor.CreateInstance(bytes));
        }

47 48 49
        /// <summary>
        /// Constructs a JsonFormatReader to parse Json into a message
        /// </summary>
50 51 52 53 54
        public static JsonFormatReader CreateInstance(string jsonText)
        {
            return new JsonFormatReader(JsonCursor.CreateInstance(jsonText));
        }

55 56 57
        /// <summary>
        /// Constructs a JsonFormatReader to parse Json into a message
        /// </summary>
58 59 60 61
        public static JsonFormatReader CreateInstance(TextReader input)
        {
            return new JsonFormatReader(JsonCursor.CreateInstance(input));
        }
62 63 64 65 66

        /// <summary>
        /// Constructs a JsonFormatReader to parse Json into a message
        /// </summary>
        internal JsonFormatReader(JsonCursor input)
67
        {
68
            _input = input;
69 70 71 72 73
            _stopChar = new Stack<int>();
            _stopChar.Push(-1);
            _state = ReaderState.Start;
        }

74 75 76 77 78
        /// <summary>
        /// Constructs a JsonFormatReader to parse Json into a message
        /// </summary>
        protected JsonFormatReader(TextReader input)
            : this(JsonCursor.CreateInstance(input))
79 80
        {
        }
81

82 83 84
        /// <summary>
        /// Returns true if the reader is currently on an array element
        /// </summary>
85 86 87 88
        public bool IsArrayMessage
        {
            get { return _input.NextChar == '['; }
        }
89 90 91 92 93 94 95 96 97 98

        /// <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))
99
            {
100
                yield return this;
101
            }
102 103 104
        }

        /// <summary>
105
        /// Reads the root-message preamble specific to this formatter
106
        /// </summary>
107
        public override void ReadMessageStart()
108 109 110 111 112
        {
            _input.Consume('{');
            _stopChar.Push('}');

            _state = ReaderState.BeginObject;
113 114 115 116 117
        }

        /// <summary>
        /// Reads the root-message close specific to this formatter
        /// </summary>
118
        public override void ReadMessageEnd()
119 120
        {
            _input.Consume((char)_stopChar.Pop());
121
            _state = ReaderState.EndValue;
122 123 124 125 126 127 128
        }

        /// <summary>
        /// Merges the contents of stream into the provided message builder
        /// </summary>
        public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
        {
129 130 131
            ReadMessageStart();
            builder.WeakMergeFrom(this, registry);
            ReadMessageEnd();
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
            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;
155 156
            if (_state == ReaderState.BeginValue)
            {
157
                return true;
158
            }
159 160 161

            int next = _input.NextChar;
            if (next == _stopChar.Peek())
162
            {
163
                return false;
164
            }
165 166 167 168 169

            _input.Assert(next != -1, "Unexpected end of file.");

            //not sure about this yet, it will allow {, "a":true }
            if (_state == ReaderState.EndValue && !_input.TryConsume(','))
170
            {
171
                return false;
172
            }
173 174 175 176 177 178 179 180 181 182 183 184 185

            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;
186
            JsonCursor.JsType type = _input.ReadVariant(out temp);
187 188
            _state = ReaderState.EndValue;

189 190
            _input.Assert(type != JsonCursor.JsType.Array && type != JsonCursor.JsType.Object,
                          "Encountered {0} while expecting {1}", type, typeInfo);
191
            if (type == JsonCursor.JsType.Null)
192
            {
193
                return false;
194 195 196 197 198 199 200 201 202 203 204 205 206
            }
            if (type == JsonCursor.JsType.True)
            {
                value = "1";
            }
            else if (type == JsonCursor.JsType.False)
            {
                value = "0";
            }
            else
            {
                value = temp as string;
            }
207 208

            //exponent representation of integer number:
209
            if (value != null && type == JsonCursor.JsType.Number &&
210
                (typeInfo != typeof(double) && typeInfo != typeof(float)) &&
211 212
                value.IndexOf("e", StringComparison.OrdinalIgnoreCase) > 0)
            {
213
                value = XmlConvert.ToString((long) Math.Round(XmlConvert.ToDouble(value), 0));
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
            }
            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;
244 245
                if (!_input.TryConsume(','))
                {
246
                    break;
247
                }
248
            }
249
            _input.Consume((char) _stopChar.Pop());
250 251 252 253 254 255 256 257 258 259 260 261
            _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;
        }
    }
262
}