blob: 2808ff5cad0b841df5a0808f45d92a4d666cc09f [file] [log] [blame]
csharptestafe844b2011-06-10 16:03:22 -05001using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Xml;
5
6namespace Google.ProtocolBuffers.Serialization
7{
8 /// <summary>
9 /// JsonFormatReader is used to parse Json into a message or an array of messages
10 /// </summary>
11 public class JsonFormatReader : AbstractTextReader
12 {
csharptest7fc785c2011-06-10 23:54:53 -050013 private readonly JsonCursor _input;
csharptestafe844b2011-06-10 16:03:22 -050014 private readonly Stack<int> _stopChar;
15
16 enum ReaderState { Start, BeginValue, EndValue, BeginObject, BeginArray }
17 string _current;
18 ReaderState _state;
19
20 /// <summary>
csharptest7fc785c2011-06-10 23:54:53 -050021 /// Constructs a JsonFormatReader to parse Json into a message, this method does not use text encoding, all bytes MUST
22 /// represent ASCII character values.
csharptestafe844b2011-06-10 16:03:22 -050023 /// </summary>
csharptest7fc785c2011-06-10 23:54:53 -050024 public static JsonFormatReader CreateInstance(Stream stream) { return new JsonFormatReader(JsonCursor.CreateInstance(stream)); }
25 /// <summary>
26 /// Constructs a JsonFormatReader to parse Json into a message, this method does not use text encoding, all bytes MUST
27 /// represent ASCII character values.
28 /// </summary>
29 public static JsonFormatReader CreateInstance(byte[] bytes) { return new JsonFormatReader(JsonCursor.CreateInstance(bytes)); }
csharptestafe844b2011-06-10 16:03:22 -050030 /// <summary>
31 /// Constructs a JsonFormatReader to parse Json into a message
32 /// </summary>
csharptest7fc785c2011-06-10 23:54:53 -050033 public static JsonFormatReader CreateInstance(string jsonText) { return new JsonFormatReader(JsonCursor.CreateInstance(jsonText)); }
34 /// <summary>
35 /// Constructs a JsonFormatReader to parse Json into a message
36 /// </summary>
37 public static JsonFormatReader CreateInstance(TextReader input) { return new JsonFormatReader(JsonCursor.CreateInstance(input)); }
38
39 /// <summary>
40 /// Constructs a JsonFormatReader to parse Json into a message
41 /// </summary>
42 internal JsonFormatReader(JsonCursor input)
csharptestafe844b2011-06-10 16:03:22 -050043 {
csharptest7fc785c2011-06-10 23:54:53 -050044 _input = input;
csharptestafe844b2011-06-10 16:03:22 -050045 _stopChar = new Stack<int>();
46 _stopChar.Push(-1);
47 _state = ReaderState.Start;
48 }
49
50 /// <summary>
csharptest7fc785c2011-06-10 23:54:53 -050051 /// Constructs a JsonFormatReader to parse Json into a message
52 /// </summary>
53 protected JsonFormatReader(TextReader input)
54 : this(JsonCursor.CreateInstance(input))
55 { }
56
57 /// <summary>
csharptestafe844b2011-06-10 16:03:22 -050058 /// Returns true if the reader is currently on an array element
59 /// </summary>
60 public bool IsArrayMessage { get { return _input.NextChar == '['; } }
61
62 /// <summary>
63 /// Returns an enumerator that is used to cursor over an array of messages
64 /// </summary>
65 /// <remarks>
66 /// This is generally used when receiving an array of messages rather than a single root message
67 /// </remarks>
68 public IEnumerable<JsonFormatReader> EnumerateArray()
69 {
70 foreach (string ignored in ForeachArrayItem(_current))
71 yield return this;
72 }
73
74 /// <summary>
75 /// Merges the contents of stream into the provided message builder
76 /// </summary>
77 public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
78 {
79 _input.Consume('{');
80 _stopChar.Push('}');
81
82 _state = ReaderState.BeginObject;
83 builder.WeakMergeFrom(this, registry);
84 _input.Consume((char)_stopChar.Pop());
85 _state = ReaderState.EndValue;
86 return builder;
87 }
88
89 /// <summary>
90 /// Causes the reader to skip past this field
91 /// </summary>
92 protected override void Skip()
93 {
94 object temp;
95 _input.ReadVariant(out temp);
96 _state = ReaderState.EndValue;
97 }
98
99 /// <summary>
100 /// Peeks at the next field in the input stream and returns what information is available.
101 /// </summary>
102 /// <remarks>
103 /// This may be called multiple times without actually reading the field. Only after the field
104 /// is either read, or skipped, should PeekNext return a different value.
105 /// </remarks>
106 protected override bool PeekNext(out string field)
107 {
108 field = _current;
109 if(_state == ReaderState.BeginValue)
110 return true;
111
112 int next = _input.NextChar;
113 if (next == _stopChar.Peek())
114 return false;
115
116 _input.Assert(next != -1, "Unexpected end of file.");
117
118 //not sure about this yet, it will allow {, "a":true }
119 if (_state == ReaderState.EndValue && !_input.TryConsume(','))
120 return false;
121
122 field = _current = _input.ReadString();
123 _input.Consume(':');
124 _state = ReaderState.BeginValue;
125 return true;
126 }
127
128 /// <summary>
129 /// Returns true if it was able to read a String from the input
130 /// </summary>
131 protected override bool ReadAsText(ref string value, Type typeInfo)
132 {
133 object temp;
csharptest7fc785c2011-06-10 23:54:53 -0500134 JsonCursor.JsType type = _input.ReadVariant(out temp);
csharptestafe844b2011-06-10 16:03:22 -0500135 _state = ReaderState.EndValue;
136
csharptest7fc785c2011-06-10 23:54:53 -0500137 _input.Assert(type != JsonCursor.JsType.Array && type != JsonCursor.JsType.Object, "Encountered {0} while expecting {1}", type, typeInfo);
138 if (type == JsonCursor.JsType.Null)
csharptestafe844b2011-06-10 16:03:22 -0500139 return false;
csharptest7fc785c2011-06-10 23:54:53 -0500140 if (type == JsonCursor.JsType.True) value = "1";
141 else if (type == JsonCursor.JsType.False) value = "0";
csharptestafe844b2011-06-10 16:03:22 -0500142 else value = temp as string;
143
144 //exponent representation of integer number:
csharptest7fc785c2011-06-10 23:54:53 -0500145 if (value != null && type == JsonCursor.JsType.Number &&
csharptestafe844b2011-06-10 16:03:22 -0500146 (typeInfo != typeof(double) && typeInfo != typeof(float)) &&
147 value.IndexOf("e", StringComparison.OrdinalIgnoreCase) > 0)
148 {
149 value = XmlConvert.ToString((long)Math.Round(XmlConvert.ToDouble(value), 0));
150 }
151 return value != null;
152 }
153
154 /// <summary>
155 /// Returns true if it was able to read a ByteString from the input
156 /// </summary>
157 protected override bool Read(ref ByteString value)
158 {
159 string bytes = null;
160 if (Read(ref bytes))
161 {
162 value = ByteString.FromBase64(bytes);
163 return true;
164 }
165 return false;
166 }
167
168 /// <summary>
169 /// Cursors through the array elements and stops at the end of the array
170 /// </summary>
171 protected override IEnumerable<string> ForeachArrayItem(string field)
172 {
173 _input.Consume('[');
174 _stopChar.Push(']');
175 _state = ReaderState.BeginArray;
176 while (_input.NextChar != ']')
177 {
178 _current = field;
179 yield return field;
180 if(!_input.TryConsume(','))
181 break;
182 }
183 _input.Consume((char)_stopChar.Pop());
184 _state = ReaderState.EndValue;
185 }
186
187 /// <summary>
188 /// Merges the input stream into the provided IBuilderLite
189 /// </summary>
190 protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
191 {
192 Merge(builder, registry);
193 return true;
194 }
195
196 }
197}