blob: e9df9138992a62379157b1a1e9e020ef9289c57b [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 {
13 private readonly JsonTextCursor _input;
14 private readonly Stack<int> _stopChar;
15
16 enum ReaderState { Start, BeginValue, EndValue, BeginObject, BeginArray }
17 string _current;
18 ReaderState _state;
19
20 /// <summary>
21 /// Constructs a JsonFormatReader to parse Json into a message
22 /// </summary>
23 public JsonFormatReader(string jsonText)
24 {
25 _input = new JsonTextCursor(jsonText.ToCharArray());
26 _stopChar = new Stack<int>();
27 _stopChar.Push(-1);
28 _state = ReaderState.Start;
29 }
30 /// <summary>
31 /// Constructs a JsonFormatReader to parse Json into a message
32 /// </summary>
33 public JsonFormatReader(TextReader input)
34 {
35 _input = new JsonTextCursor(input);
36 _stopChar = new Stack<int>();
37 _stopChar.Push(-1);
38 _state = ReaderState.Start;
39 }
40
41 /// <summary>
42 /// Returns true if the reader is currently on an array element
43 /// </summary>
44 public bool IsArrayMessage { get { return _input.NextChar == '['; } }
45
46 /// <summary>
47 /// Returns an enumerator that is used to cursor over an array of messages
48 /// </summary>
49 /// <remarks>
50 /// This is generally used when receiving an array of messages rather than a single root message
51 /// </remarks>
52 public IEnumerable<JsonFormatReader> EnumerateArray()
53 {
54 foreach (string ignored in ForeachArrayItem(_current))
55 yield return this;
56 }
57
58 /// <summary>
59 /// Merges the contents of stream into the provided message builder
60 /// </summary>
61 public override TBuilder Merge<TBuilder>(TBuilder builder, ExtensionRegistry registry)
62 {
63 _input.Consume('{');
64 _stopChar.Push('}');
65
66 _state = ReaderState.BeginObject;
67 builder.WeakMergeFrom(this, registry);
68 _input.Consume((char)_stopChar.Pop());
69 _state = ReaderState.EndValue;
70 return builder;
71 }
72
73 /// <summary>
74 /// Causes the reader to skip past this field
75 /// </summary>
76 protected override void Skip()
77 {
78 object temp;
79 _input.ReadVariant(out temp);
80 _state = ReaderState.EndValue;
81 }
82
83 /// <summary>
84 /// Peeks at the next field in the input stream and returns what information is available.
85 /// </summary>
86 /// <remarks>
87 /// This may be called multiple times without actually reading the field. Only after the field
88 /// is either read, or skipped, should PeekNext return a different value.
89 /// </remarks>
90 protected override bool PeekNext(out string field)
91 {
92 field = _current;
93 if(_state == ReaderState.BeginValue)
94 return true;
95
96 int next = _input.NextChar;
97 if (next == _stopChar.Peek())
98 return false;
99
100 _input.Assert(next != -1, "Unexpected end of file.");
101
102 //not sure about this yet, it will allow {, "a":true }
103 if (_state == ReaderState.EndValue && !_input.TryConsume(','))
104 return false;
105
106 field = _current = _input.ReadString();
107 _input.Consume(':');
108 _state = ReaderState.BeginValue;
109 return true;
110 }
111
112 /// <summary>
113 /// Returns true if it was able to read a String from the input
114 /// </summary>
115 protected override bool ReadAsText(ref string value, Type typeInfo)
116 {
117 object temp;
118 JsonTextCursor.JsType type = _input.ReadVariant(out temp);
119 _state = ReaderState.EndValue;
120
121 _input.Assert(type != JsonTextCursor.JsType.Array && type != JsonTextCursor.JsType.Object, "Encountered {0} while expecting {1}", type, typeInfo);
122 if (type == JsonTextCursor.JsType.Null)
123 return false;
124 if (type == JsonTextCursor.JsType.True) value = "1";
125 else if (type == JsonTextCursor.JsType.False) value = "0";
126 else value = temp as string;
127
128 //exponent representation of integer number:
129 if (value != null && type == JsonTextCursor.JsType.Number &&
130 (typeInfo != typeof(double) && typeInfo != typeof(float)) &&
131 value.IndexOf("e", StringComparison.OrdinalIgnoreCase) > 0)
132 {
133 value = XmlConvert.ToString((long)Math.Round(XmlConvert.ToDouble(value), 0));
134 }
135 return value != null;
136 }
137
138 /// <summary>
139 /// Returns true if it was able to read a ByteString from the input
140 /// </summary>
141 protected override bool Read(ref ByteString value)
142 {
143 string bytes = null;
144 if (Read(ref bytes))
145 {
146 value = ByteString.FromBase64(bytes);
147 return true;
148 }
149 return false;
150 }
151
152 /// <summary>
153 /// Cursors through the array elements and stops at the end of the array
154 /// </summary>
155 protected override IEnumerable<string> ForeachArrayItem(string field)
156 {
157 _input.Consume('[');
158 _stopChar.Push(']');
159 _state = ReaderState.BeginArray;
160 while (_input.NextChar != ']')
161 {
162 _current = field;
163 yield return field;
164 if(!_input.TryConsume(','))
165 break;
166 }
167 _input.Consume((char)_stopChar.Pop());
168 _state = ReaderState.EndValue;
169 }
170
171 /// <summary>
172 /// Merges the input stream into the provided IBuilderLite
173 /// </summary>
174 protected override bool ReadMessage(IBuilderLite builder, ExtensionRegistry registry)
175 {
176 Merge(builder, registry);
177 return true;
178 }
179
180 }
181}