blob: 403367876eaef15577a8fe6b5255580fac8eb7b5 [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
csharptest74c5e0c2011-07-14 13:06:22 -050016 private enum ReaderState
17 {
18 Start,
19 BeginValue,
20 EndValue,
21 BeginObject,
22 BeginArray
23 }
24
25 private string _current;
26 private ReaderState _state;
csharptestafe844b2011-06-10 16:03:22 -050027
28 /// <summary>
csharptest7fc785c2011-06-10 23:54:53 -050029 /// Constructs a JsonFormatReader to parse Json into a message, this method does not use text encoding, all bytes MUST
30 /// represent ASCII character values.
csharptestafe844b2011-06-10 16:03:22 -050031 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050032 public static JsonFormatReader CreateInstance(Stream stream)
33 {
34 return new JsonFormatReader(JsonCursor.CreateInstance(stream));
35 }
36
csharptest7fc785c2011-06-10 23:54:53 -050037 /// <summary>
38 /// Constructs a JsonFormatReader to parse Json into a message, this method does not use text encoding, all bytes MUST
39 /// represent ASCII character values.
40 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050041 public static JsonFormatReader CreateInstance(byte[] bytes)
42 {
43 return new JsonFormatReader(JsonCursor.CreateInstance(bytes));
44 }
45
csharptestafe844b2011-06-10 16:03:22 -050046 /// <summary>
47 /// Constructs a JsonFormatReader to parse Json into a message
48 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050049 public static JsonFormatReader CreateInstance(string jsonText)
50 {
51 return new JsonFormatReader(JsonCursor.CreateInstance(jsonText));
52 }
53
csharptest7fc785c2011-06-10 23:54:53 -050054 /// <summary>
55 /// Constructs a JsonFormatReader to parse Json into a message
56 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050057 public static JsonFormatReader CreateInstance(TextReader input)
58 {
59 return new JsonFormatReader(JsonCursor.CreateInstance(input));
60 }
csharptest7fc785c2011-06-10 23:54:53 -050061
62 /// <summary>
63 /// Constructs a JsonFormatReader to parse Json into a message
64 /// </summary>
65 internal JsonFormatReader(JsonCursor input)
csharptestafe844b2011-06-10 16:03:22 -050066 {
csharptest7fc785c2011-06-10 23:54:53 -050067 _input = input;
csharptestafe844b2011-06-10 16:03:22 -050068 _stopChar = new Stack<int>();
69 _stopChar.Push(-1);
70 _state = ReaderState.Start;
71 }
72
73 /// <summary>
csharptest7fc785c2011-06-10 23:54:53 -050074 /// Constructs a JsonFormatReader to parse Json into a message
75 /// </summary>
76 protected JsonFormatReader(TextReader input)
77 : this(JsonCursor.CreateInstance(input))
csharptest74c5e0c2011-07-14 13:06:22 -050078 {
79 }
csharptest7fc785c2011-06-10 23:54:53 -050080
81 /// <summary>
csharptestafe844b2011-06-10 16:03:22 -050082 /// Returns true if the reader is currently on an array element
83 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -050084 public bool IsArrayMessage
85 {
86 get { return _input.NextChar == '['; }
87 }
csharptestafe844b2011-06-10 16:03:22 -050088
89 /// <summary>
90 /// Returns an enumerator that is used to cursor over an array of messages
91 /// </summary>
92 /// <remarks>
93 /// This is generally used when receiving an array of messages rather than a single root message
94 /// </remarks>
95 public IEnumerable<JsonFormatReader> EnumerateArray()
96 {
97 foreach (string ignored in ForeachArrayItem(_current))
csharptest74c5e0c2011-07-14 13:06:22 -050098 {
csharptestafe844b2011-06-10 16:03:22 -050099 yield return this;
csharptest74c5e0c2011-07-14 13:06:22 -0500100 }
csharptestafe844b2011-06-10 16:03:22 -0500101 }
102
103 /// <summary>
csharptestc2d2c1a2011-09-08 20:02:11 -0500104 /// Reads the root-message preamble specific to this formatter
csharptestafe844b2011-06-10 16:03:22 -0500105 /// </summary>
csharptestc2d2c1a2011-09-08 20:02:11 -0500106 public override AbstractReader ReadStartMessage()
csharptestafe844b2011-06-10 16:03:22 -0500107 {
108 _input.Consume('{');
109 _stopChar.Push('}');
110
111 _state = ReaderState.BeginObject;
csharptestc2d2c1a2011-09-08 20:02:11 -0500112 return this;
113 }
114
115 /// <summary>
116 /// Reads the root-message close specific to this formatter
117 /// </summary>
118 public override void ReadEndMessage()
119 {
120 _input.Consume((char)_stopChar.Pop());
csharptestafe844b2011-06-10 16:03:22 -0500121 _state = ReaderState.EndValue;
csharptestc2d2c1a2011-09-08 20:02:11 -0500122 }
123
124 /// <summary>
125 /// Merges the contents of stream into the provided message builder
126 /// </summary>
127 public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
128 {
129 AbstractReader rdr = ReadStartMessage();
130 builder.WeakMergeFrom(rdr, registry);
131 rdr.ReadEndMessage();
csharptestafe844b2011-06-10 16:03:22 -0500132 return builder;
133 }
134
135 /// <summary>
136 /// Causes the reader to skip past this field
137 /// </summary>
138 protected override void Skip()
139 {
140 object temp;
141 _input.ReadVariant(out temp);
142 _state = ReaderState.EndValue;
143 }
144
145 /// <summary>
146 /// Peeks at the next field in the input stream and returns what information is available.
147 /// </summary>
148 /// <remarks>
149 /// This may be called multiple times without actually reading the field. Only after the field
150 /// is either read, or skipped, should PeekNext return a different value.
151 /// </remarks>
152 protected override bool PeekNext(out string field)
153 {
154 field = _current;
csharptest74c5e0c2011-07-14 13:06:22 -0500155 if (_state == ReaderState.BeginValue)
156 {
csharptestafe844b2011-06-10 16:03:22 -0500157 return true;
csharptest74c5e0c2011-07-14 13:06:22 -0500158 }
csharptestafe844b2011-06-10 16:03:22 -0500159
160 int next = _input.NextChar;
161 if (next == _stopChar.Peek())
csharptest74c5e0c2011-07-14 13:06:22 -0500162 {
csharptestafe844b2011-06-10 16:03:22 -0500163 return false;
csharptest74c5e0c2011-07-14 13:06:22 -0500164 }
csharptestafe844b2011-06-10 16:03:22 -0500165
166 _input.Assert(next != -1, "Unexpected end of file.");
167
168 //not sure about this yet, it will allow {, "a":true }
169 if (_state == ReaderState.EndValue && !_input.TryConsume(','))
csharptest74c5e0c2011-07-14 13:06:22 -0500170 {
csharptestafe844b2011-06-10 16:03:22 -0500171 return false;
csharptest74c5e0c2011-07-14 13:06:22 -0500172 }
csharptestafe844b2011-06-10 16:03:22 -0500173
174 field = _current = _input.ReadString();
175 _input.Consume(':');
176 _state = ReaderState.BeginValue;
177 return true;
178 }
179
180 /// <summary>
181 /// Returns true if it was able to read a String from the input
182 /// </summary>
183 protected override bool ReadAsText(ref string value, Type typeInfo)
184 {
185 object temp;
csharptest7fc785c2011-06-10 23:54:53 -0500186 JsonCursor.JsType type = _input.ReadVariant(out temp);
csharptestafe844b2011-06-10 16:03:22 -0500187 _state = ReaderState.EndValue;
188
csharptest74c5e0c2011-07-14 13:06:22 -0500189 _input.Assert(type != JsonCursor.JsType.Array && type != JsonCursor.JsType.Object,
190 "Encountered {0} while expecting {1}", type, typeInfo);
csharptest7fc785c2011-06-10 23:54:53 -0500191 if (type == JsonCursor.JsType.Null)
csharptest74c5e0c2011-07-14 13:06:22 -0500192 {
csharptestafe844b2011-06-10 16:03:22 -0500193 return false;
csharptest74c5e0c2011-07-14 13:06:22 -0500194 }
195 if (type == JsonCursor.JsType.True)
196 {
197 value = "1";
198 }
199 else if (type == JsonCursor.JsType.False)
200 {
201 value = "0";
202 }
203 else
204 {
205 value = temp as string;
206 }
csharptestafe844b2011-06-10 16:03:22 -0500207
208 //exponent representation of integer number:
csharptest7fc785c2011-06-10 23:54:53 -0500209 if (value != null && type == JsonCursor.JsType.Number &&
csharptest74c5e0c2011-07-14 13:06:22 -0500210 (typeInfo != typeof (double) && typeInfo != typeof (float)) &&
csharptestafe844b2011-06-10 16:03:22 -0500211 value.IndexOf("e", StringComparison.OrdinalIgnoreCase) > 0)
212 {
csharptest74c5e0c2011-07-14 13:06:22 -0500213 value = XmlConvert.ToString((long) Math.Round(XmlConvert.ToDouble(value), 0));
csharptestafe844b2011-06-10 16:03:22 -0500214 }
215 return value != null;
216 }
217
218 /// <summary>
219 /// Returns true if it was able to read a ByteString from the input
220 /// </summary>
221 protected override bool Read(ref ByteString value)
222 {
223 string bytes = null;
224 if (Read(ref bytes))
225 {
226 value = ByteString.FromBase64(bytes);
227 return true;
228 }
229 return false;
230 }
231
232 /// <summary>
233 /// Cursors through the array elements and stops at the end of the array
234 /// </summary>
235 protected override IEnumerable<string> ForeachArrayItem(string field)
236 {
237 _input.Consume('[');
238 _stopChar.Push(']');
239 _state = ReaderState.BeginArray;
240 while (_input.NextChar != ']')
241 {
242 _current = field;
243 yield return field;
csharptest74c5e0c2011-07-14 13:06:22 -0500244 if (!_input.TryConsume(','))
245 {
csharptestafe844b2011-06-10 16:03:22 -0500246 break;
csharptest74c5e0c2011-07-14 13:06:22 -0500247 }
csharptestafe844b2011-06-10 16:03:22 -0500248 }
csharptest74c5e0c2011-07-14 13:06:22 -0500249 _input.Consume((char) _stopChar.Pop());
csharptestafe844b2011-06-10 16:03:22 -0500250 _state = ReaderState.EndValue;
251 }
252
253 /// <summary>
254 /// Merges the input stream into the provided IBuilderLite
255 /// </summary>
256 protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
257 {
258 Merge(builder, registry);
259 return true;
260 }
csharptestafe844b2011-06-10 16:03:22 -0500261 }
csharptest74c5e0c2011-07-14 13:06:22 -0500262}