blob: b2057524cc463f8c39730adb0ac39746a74eb718 [file] [log] [blame]
csharptestafe844b2011-06-10 16:03:22 -05001using System;
2using System.Collections.Generic;
3using System.IO;
csharptest7fc785c2011-06-10 23:54:53 -05004using System.Text;
csharptestafe844b2011-06-10 16:03:22 -05005using Google.ProtocolBuffers.Descriptors;
6
7namespace Google.ProtocolBuffers.Serialization
8{
9 /// <summary>
10 /// JsonFormatWriter is a .NET 2.0 friendly json formatter for proto buffer messages. For .NET 3.5
11 /// you may also use the XmlFormatWriter with an XmlWriter created by the
12 /// <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory">JsonReaderWriterFactory</see>.
13 /// </summary>
csharptest7fc785c2011-06-10 23:54:53 -050014 public abstract class JsonFormatWriter : AbstractTextWriter
csharptestafe844b2011-06-10 16:03:22 -050015 {
csharptest7fc785c2011-06-10 23:54:53 -050016 #region buffering implementations
17 private class JsonTextWriter : JsonFormatWriter
csharptestafe844b2011-06-10 16:03:22 -050018 {
csharptest7fc785c2011-06-10 23:54:53 -050019 private readonly char[] _buffer;
20 private TextWriter _output;
21 int _bufferPos;
csharptestafe844b2011-06-10 16:03:22 -050022
csharptest7fc785c2011-06-10 23:54:53 -050023 public JsonTextWriter(TextWriter output)
csharptestafe844b2011-06-10 16:03:22 -050024 {
csharptest7fc785c2011-06-10 23:54:53 -050025 _buffer = new char[4096];
26 _bufferPos = 0;
27 _output = output;
28 _counter.Add(0);
29 }
30
31 /// <summary>
32 /// Returns the output of TextWriter.ToString() where TextWriter is the ctor argument.
33 /// </summary>
34 public override string ToString()
35 {
36 Flush();
37
38 if (_output != null)
39 return _output.ToString();
40
41 return new String(_buffer, 0, _bufferPos);
42 }
43
44 protected override void WriteToOutput(char[] chars, int offset, int len)
45 {
46 if (_bufferPos + len >= _buffer.Length)
csharptestafe844b2011-06-10 16:03:22 -050047 {
csharptest7fc785c2011-06-10 23:54:53 -050048 if (_output == null)
49 _output = new StringWriter(new System.Text.StringBuilder(_buffer.Length * 2 + len));
50 Flush();
51 }
52
53 if (len < _buffer.Length)
54 {
55 if (len <= 12)
56 {
57 int stop = offset + len;
58 for (int i = offset; i < stop; i++)
59 _buffer[_bufferPos++] = chars[i];
60 }
61 else
62 {
63 Buffer.BlockCopy(chars, offset << 1, _buffer, _bufferPos << 1, len << 1);
64 _bufferPos += len;
65 }
66 }
67 else
68 _output.Write(chars, offset, len);
69 }
70
71 protected override void WriteToOutput(char ch)
72 {
73 if (_bufferPos >= _buffer.Length)
74 Flush();
75 _buffer[_bufferPos++] = ch;
76 }
77
78 public override void Flush()
79 {
80 if (_bufferPos > 0 && _output != null)
81 {
82 _output.Write(_buffer, 0, _bufferPos);
83 _bufferPos = 0;
84 }
85 base.Flush();
86 }
87 }
88 private class JsonStreamWriter : JsonFormatWriter
89 {
90#if SILVERLIGHT2 || COMPACT_FRAMEWORK_35
91 static readonly Encoding Encoding = Encoding.UTF8;
92#else
93 static readonly Encoding Encoding = Encoding.ASCII;
94#endif
95 private readonly byte[] _buffer;
96 private Stream _output;
97 int _bufferPos;
98
99 public JsonStreamWriter(Stream output)
100 {
101 _buffer = new byte[8192];
102 _bufferPos = 0;
103 _output = output;
104 _counter.Add(0);
105 }
106
107 protected override void WriteToOutput(char[] chars, int offset, int len)
108 {
109 if (_bufferPos + len >= _buffer.Length)
110 Flush();
111
112 if (len < _buffer.Length)
113 {
114 if (len <= 12)
115 {
116 int stop = offset + len;
117 for (int i = offset; i < stop; i++)
118 _buffer[_bufferPos++] = (byte)chars[i];
119 }
120 else
121 {
122 _bufferPos += Encoding.GetBytes(chars, offset, len, _buffer, _bufferPos);
123 }
csharptestafe844b2011-06-10 16:03:22 -0500124 }
125 else
126 {
csharptest7fc785c2011-06-10 23:54:53 -0500127 byte[] temp = Encoding.GetBytes(chars, offset, len);
128 _output.Write(temp, 0, temp.Length);
csharptestafe844b2011-06-10 16:03:22 -0500129 }
130 }
csharptestafe844b2011-06-10 16:03:22 -0500131
csharptest7fc785c2011-06-10 23:54:53 -0500132 protected override void WriteToOutput(char ch)
csharptestafe844b2011-06-10 16:03:22 -0500133 {
csharptest7fc785c2011-06-10 23:54:53 -0500134 if (_bufferPos >= _buffer.Length)
135 Flush();
136 _buffer[_bufferPos++] = (byte)ch;
csharptestafe844b2011-06-10 16:03:22 -0500137 }
csharptest7fc785c2011-06-10 23:54:53 -0500138
139 public override void Flush()
140 {
141 if (_bufferPos > 0 && _output != null)
142 {
143 _output.Write(_buffer, 0, _bufferPos);
144 _bufferPos = 0;
145 }
146 base.Flush();
147 }
148 }
149 #endregion
150
151 private readonly List<int> _counter;
152 private bool _isArray;
153 /// <summary>
154 /// Constructs a JsonFormatWriter, use the ToString() member to extract the final Json on completion.
155 /// </summary>
156 protected JsonFormatWriter()
157 {
158 _counter = new List<int>();
csharptestafe844b2011-06-10 16:03:22 -0500159 }
160
161 /// <summary>
csharptest7fc785c2011-06-10 23:54:53 -0500162 /// Constructs a JsonFormatWriter, use ToString() to extract the final output
csharptestafe844b2011-06-10 16:03:22 -0500163 /// </summary>
csharptest7fc785c2011-06-10 23:54:53 -0500164 public static JsonFormatWriter CreateInstance() { return new JsonTextWriter(null); }
165
166 /// <summary>
167 /// Constructs a JsonFormatWriter to output to the given text writer
168 /// </summary>
169 public static JsonFormatWriter CreateInstance(TextWriter output) { return new JsonTextWriter(output); }
170
171 /// <summary>
172 /// Constructs a JsonFormatWriter to output to the given stream
173 /// </summary>
174 public static JsonFormatWriter CreateInstance(Stream output) { return new JsonStreamWriter(output); }
175
176 /// <summary> Write to the output stream </summary>
177 protected void WriteToOutput(string format, params object[] args)
178 { WriteToOutput(String.Format(format, args)); }
179 /// <summary> Write to the output stream </summary>
180 protected void WriteToOutput(string text)
181 { WriteToOutput(text.ToCharArray(), 0, text.Length); }
182 /// <summary> Write to the output stream </summary>
183 protected abstract void WriteToOutput(char ch);
184 /// <summary> Write to the output stream </summary>
185 protected abstract void WriteToOutput(char[] chars, int offset, int len);
csharptestafe844b2011-06-10 16:03:22 -0500186
187 /// <summary> Sets the output formatting to use Environment.NewLine with 4-character indentions </summary>
188 public JsonFormatWriter Formatted()
189 {
190 NewLine = Environment.NewLine;
191 Indent = " ";
192 Whitespace = " ";
193 return this;
194 }
195
196 /// <summary> Gets or sets the characters to use for the new-line, default = empty </summary>
197 public string NewLine { get; set; }
198 /// <summary> Gets or sets the text to use for indenting, default = empty </summary>
199 public string Indent { get; set; }
200 /// <summary> Gets or sets the whitespace to use to separate the text, default = empty </summary>
201 public string Whitespace { get; set; }
202
203 private void Seperator()
204 {
205 if (_counter.Count == 0)
206 throw new InvalidOperationException("Missmatched open/close in Json writer.");
207
208 int index = _counter.Count - 1;
209 if (_counter[index] > 0)
210 WriteToOutput(',');
211
212 WriteLine(String.Empty);
213 _counter[index] = _counter[index] + 1;
214 }
215
216 private void WriteLine(string content)
217 {
218 if (!String.IsNullOrEmpty(NewLine))
219 {
220 WriteToOutput(NewLine);
221 for (int i = 1; i < _counter.Count; i++)
222 WriteToOutput(Indent);
223 }
224 else if(!String.IsNullOrEmpty(Whitespace))
225 WriteToOutput(Whitespace);
226
227 WriteToOutput(content);
228 }
229
230 private void WriteName(string field)
231 {
232 Seperator();
233 if (!String.IsNullOrEmpty(field))
234 {
235 WriteToOutput('"');
236 WriteToOutput(field);
237 WriteToOutput('"');
238 WriteToOutput(':');
239 if (!String.IsNullOrEmpty(Whitespace))
240 WriteToOutput(Whitespace);
241 }
242 }
243
244 private void EncodeText(string value)
245 {
246 char[] text = value.ToCharArray();
247 int len = text.Length;
248 int pos = 0;
249
250 while (pos < len)
251 {
252 int next = pos;
253 while (next < len && text[next] >= 32 && text[next] < 127 && text[next] != '\\' && text[next] != '/' && text[next] != '"')
254 next++;
255 WriteToOutput(text, pos, next - pos);
256 if (next < len)
257 {
258 switch (text[next])
259 {
260 case '"': WriteToOutput(@"\"""); break;
261 case '\\': WriteToOutput(@"\\"); break;
262 //odd at best to escape '/', most Json implementations don't, but it is defined in the rfc-4627
263 case '/': WriteToOutput(@"\/"); break;
264 case '\b': WriteToOutput(@"\b"); break;
265 case '\f': WriteToOutput(@"\f"); break;
266 case '\n': WriteToOutput(@"\n"); break;
267 case '\r': WriteToOutput(@"\r"); break;
268 case '\t': WriteToOutput(@"\t"); break;
269 default: WriteToOutput(@"\u{0:x4}", (int)text[next]); break;
270 }
271 next++;
272 }
273 pos = next;
274 }
275 }
276
277 /// <summary>
278 /// Writes a String value
279 /// </summary>
280 protected override void WriteAsText(string field, string textValue, object typedValue)
281 {
282 WriteName(field);
283 if(typedValue is bool || typedValue is int || typedValue is uint || typedValue is long || typedValue is ulong || typedValue is double || typedValue is float)
284 WriteToOutput(textValue);
285 else
286 {
287 WriteToOutput('"');
288 if (typedValue is string)
289 EncodeText(textValue);
290 else
291 WriteToOutput(textValue);
292 WriteToOutput('"');
293 }
294 }
295
296 /// <summary>
297 /// Writes a Double value
298 /// </summary>
299 protected override void Write(string field, double value)
300 {
301 if (double.IsNaN(value) || double.IsNegativeInfinity(value) || double.IsPositiveInfinity(value))
302 throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity");
303 base.Write(field, value);
304 }
305
306 /// <summary>
307 /// Writes a Single value
308 /// </summary>
309 protected override void Write(string field, float value)
310 {
311 if (float.IsNaN(value) || float.IsNegativeInfinity(value) || float.IsPositiveInfinity(value))
312 throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity");
313 base.Write(field, value);
314 }
315
316 // Treat enum as string
317 protected override void WriteEnum(string field, int number, string name)
318 {
319 Write(field, name);
320 }
321
322 /// <summary>
323 /// Writes an array of field values
324 /// </summary>
325 protected override void WriteArray(FieldType type, string field, System.Collections.IEnumerable items)
326 {
327 System.Collections.IEnumerator enumerator = items.GetEnumerator();
328 try { if (!enumerator.MoveNext()) return; }
329 finally { if (enumerator is IDisposable) ((IDisposable)enumerator).Dispose(); }
330
331 WriteName(field);
332 WriteToOutput("[");
333 _counter.Add(0);
334
335 base.WriteArray(type, String.Empty, items);
336
337 _counter.RemoveAt(_counter.Count - 1);
338 WriteLine("]");
339 }
340
341 /// <summary>
342 /// Writes a message
343 /// </summary>
344 protected override void WriteMessageOrGroup(string field, IMessageLite message)
345 {
346 WriteName(field);
347 WriteMessage(message);
348 }
349
350 /// <summary>
351 /// Writes the message to the the formatted stream.
352 /// </summary>
353 public override void WriteMessage(IMessageLite message)
354 {
355 if (_isArray) Seperator();
356 WriteToOutput("{");
357 _counter.Add(0);
358 message.WriteTo(this);
359 _counter.RemoveAt(_counter.Count - 1);
360 WriteLine("}");
361 Flush();
362 }
363
364 /// <summary>
csharptestafe844b2011-06-10 16:03:22 -0500365 /// Used in streaming arrays of objects to the writer
366 /// </summary>
367 /// <example>
368 /// <code>
369 /// using(writer.StartArray())
370 /// foreach(IMessageLite m in messages)
371 /// writer.WriteMessage(m);
372 /// </code>
373 /// </example>
374 public sealed class JsonArray : IDisposable
375 {
376 JsonFormatWriter _writer;
377 internal JsonArray(JsonFormatWriter writer)
378 {
379 _writer = writer;
380 _writer.WriteToOutput("[");
381 _writer._counter.Add(0);
382 }
383
384 /// <summary>
385 /// Causes the end of the array character to be written.
386 /// </summary>
387 void EndArray()
388 {
389 if (_writer != null)
390 {
391 _writer._counter.RemoveAt(_writer._counter.Count - 1);
392 _writer.WriteLine("]");
393 _writer.Flush();
394 }
395 _writer = null;
396 }
397 void IDisposable.Dispose() { EndArray(); }
398 }
399
400 /// <summary>
401 /// Used to write an array of messages as the output rather than a single message.
402 /// </summary>
403 /// <example>
404 /// <code>
405 /// using(writer.StartArray())
406 /// foreach(IMessageLite m in messages)
407 /// writer.WriteMessage(m);
408 /// </code>
409 /// </example>
410 public JsonArray StartArray()
411 {
412 if (_isArray) Seperator();
413 _isArray = true;
414 return new JsonArray(this);
415 }
416 }
417}