blob: 12d180d84bc48b73939cdcab0ccde7997a88c273 [file] [log] [blame]
csharptestafe844b2011-06-10 16:03:22 -05001using System;
csharptest74c5e0c2011-07-14 13:06:22 -05002using System.Collections;
csharptestafe844b2011-06-10 16:03:22 -05003using System.Collections.Generic;
4using System.IO;
csharptest7fc785c2011-06-10 23:54:53 -05005using System.Text;
csharptestafe844b2011-06-10 16:03:22 -05006using Google.ProtocolBuffers.Descriptors;
7
8namespace Google.ProtocolBuffers.Serialization
9{
10 /// <summary>
11 /// JsonFormatWriter is a .NET 2.0 friendly json formatter for proto buffer messages. For .NET 3.5
12 /// you may also use the XmlFormatWriter with an XmlWriter created by the
13 /// <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory">JsonReaderWriterFactory</see>.
14 /// </summary>
csharptest7fc785c2011-06-10 23:54:53 -050015 public abstract class JsonFormatWriter : AbstractTextWriter
csharptestafe844b2011-06-10 16:03:22 -050016 {
csharptest7fc785c2011-06-10 23:54:53 -050017 #region buffering implementations
csharptest74c5e0c2011-07-14 13:06:22 -050018
csharptest7fc785c2011-06-10 23:54:53 -050019 private class JsonTextWriter : JsonFormatWriter
csharptestafe844b2011-06-10 16:03:22 -050020 {
csharptest7fc785c2011-06-10 23:54:53 -050021 private readonly char[] _buffer;
22 private TextWriter _output;
csharptest74c5e0c2011-07-14 13:06:22 -050023 private int _bufferPos;
csharptestafe844b2011-06-10 16:03:22 -050024
csharptest7fc785c2011-06-10 23:54:53 -050025 public JsonTextWriter(TextWriter output)
csharptestafe844b2011-06-10 16:03:22 -050026 {
csharptest7fc785c2011-06-10 23:54:53 -050027 _buffer = new char[4096];
28 _bufferPos = 0;
29 _output = output;
30 _counter.Add(0);
31 }
32
33 /// <summary>
34 /// Returns the output of TextWriter.ToString() where TextWriter is the ctor argument.
35 /// </summary>
36 public override string ToString()
37 {
38 Flush();
39
40 if (_output != null)
csharptest74c5e0c2011-07-14 13:06:22 -050041 {
csharptest7fc785c2011-06-10 23:54:53 -050042 return _output.ToString();
csharptest74c5e0c2011-07-14 13:06:22 -050043 }
csharptest7fc785c2011-06-10 23:54:53 -050044
45 return new String(_buffer, 0, _bufferPos);
46 }
47
48 protected override void WriteToOutput(char[] chars, int offset, int len)
49 {
50 if (_bufferPos + len >= _buffer.Length)
csharptestafe844b2011-06-10 16:03:22 -050051 {
csharptest7fc785c2011-06-10 23:54:53 -050052 if (_output == null)
csharptest74c5e0c2011-07-14 13:06:22 -050053 {
54 _output = new StringWriter(new StringBuilder(_buffer.Length*2 + len));
55 }
csharptest7fc785c2011-06-10 23:54:53 -050056 Flush();
57 }
58
59 if (len < _buffer.Length)
60 {
61 if (len <= 12)
62 {
63 int stop = offset + len;
64 for (int i = offset; i < stop; i++)
csharptest74c5e0c2011-07-14 13:06:22 -050065 {
csharptest7fc785c2011-06-10 23:54:53 -050066 _buffer[_bufferPos++] = chars[i];
csharptest74c5e0c2011-07-14 13:06:22 -050067 }
csharptest7fc785c2011-06-10 23:54:53 -050068 }
69 else
70 {
71 Buffer.BlockCopy(chars, offset << 1, _buffer, _bufferPos << 1, len << 1);
72 _bufferPos += len;
73 }
74 }
75 else
csharptest74c5e0c2011-07-14 13:06:22 -050076 {
csharptest7fc785c2011-06-10 23:54:53 -050077 _output.Write(chars, offset, len);
csharptest74c5e0c2011-07-14 13:06:22 -050078 }
csharptest7fc785c2011-06-10 23:54:53 -050079 }
80
81 protected override void WriteToOutput(char ch)
82 {
83 if (_bufferPos >= _buffer.Length)
csharptest74c5e0c2011-07-14 13:06:22 -050084 {
csharptest7fc785c2011-06-10 23:54:53 -050085 Flush();
csharptest74c5e0c2011-07-14 13:06:22 -050086 }
csharptest7fc785c2011-06-10 23:54:53 -050087 _buffer[_bufferPos++] = ch;
88 }
89
90 public override void Flush()
91 {
92 if (_bufferPos > 0 && _output != null)
93 {
94 _output.Write(_buffer, 0, _bufferPos);
95 _bufferPos = 0;
96 }
97 base.Flush();
98 }
99 }
csharptest74c5e0c2011-07-14 13:06:22 -0500100
csharptest7fc785c2011-06-10 23:54:53 -0500101 private class JsonStreamWriter : JsonFormatWriter
102 {
103#if SILVERLIGHT2 || COMPACT_FRAMEWORK_35
104 static readonly Encoding Encoding = Encoding.UTF8;
105#else
csharptest74c5e0c2011-07-14 13:06:22 -0500106 private static readonly Encoding Encoding = Encoding.ASCII;
csharptest7fc785c2011-06-10 23:54:53 -0500107#endif
108 private readonly byte[] _buffer;
109 private Stream _output;
csharptest74c5e0c2011-07-14 13:06:22 -0500110 private int _bufferPos;
csharptest7fc785c2011-06-10 23:54:53 -0500111
112 public JsonStreamWriter(Stream output)
113 {
114 _buffer = new byte[8192];
115 _bufferPos = 0;
116 _output = output;
117 _counter.Add(0);
118 }
119
120 protected override void WriteToOutput(char[] chars, int offset, int len)
121 {
122 if (_bufferPos + len >= _buffer.Length)
csharptest74c5e0c2011-07-14 13:06:22 -0500123 {
csharptest7fc785c2011-06-10 23:54:53 -0500124 Flush();
csharptest74c5e0c2011-07-14 13:06:22 -0500125 }
csharptest7fc785c2011-06-10 23:54:53 -0500126
127 if (len < _buffer.Length)
128 {
129 if (len <= 12)
130 {
131 int stop = offset + len;
132 for (int i = offset; i < stop; i++)
csharptest74c5e0c2011-07-14 13:06:22 -0500133 {
134 _buffer[_bufferPos++] = (byte) chars[i];
135 }
csharptest7fc785c2011-06-10 23:54:53 -0500136 }
137 else
138 {
139 _bufferPos += Encoding.GetBytes(chars, offset, len, _buffer, _bufferPos);
140 }
csharptestafe844b2011-06-10 16:03:22 -0500141 }
142 else
143 {
csharptest7fc785c2011-06-10 23:54:53 -0500144 byte[] temp = Encoding.GetBytes(chars, offset, len);
145 _output.Write(temp, 0, temp.Length);
csharptestafe844b2011-06-10 16:03:22 -0500146 }
147 }
csharptestafe844b2011-06-10 16:03:22 -0500148
csharptest7fc785c2011-06-10 23:54:53 -0500149 protected override void WriteToOutput(char ch)
csharptestafe844b2011-06-10 16:03:22 -0500150 {
csharptest7fc785c2011-06-10 23:54:53 -0500151 if (_bufferPos >= _buffer.Length)
csharptest74c5e0c2011-07-14 13:06:22 -0500152 {
csharptest7fc785c2011-06-10 23:54:53 -0500153 Flush();
csharptest74c5e0c2011-07-14 13:06:22 -0500154 }
155 _buffer[_bufferPos++] = (byte) ch;
csharptestafe844b2011-06-10 16:03:22 -0500156 }
csharptest7fc785c2011-06-10 23:54:53 -0500157
158 public override void Flush()
159 {
160 if (_bufferPos > 0 && _output != null)
161 {
162 _output.Write(_buffer, 0, _bufferPos);
163 _bufferPos = 0;
164 }
165 base.Flush();
166 }
167 }
csharptest74c5e0c2011-07-14 13:06:22 -0500168
csharptest7fc785c2011-06-10 23:54:53 -0500169 #endregion
170
171 private readonly List<int> _counter;
172 private bool _isArray;
csharptest74c5e0c2011-07-14 13:06:22 -0500173
csharptest7fc785c2011-06-10 23:54:53 -0500174 /// <summary>
175 /// Constructs a JsonFormatWriter, use the ToString() member to extract the final Json on completion.
176 /// </summary>
177 protected JsonFormatWriter()
178 {
179 _counter = new List<int>();
csharptestafe844b2011-06-10 16:03:22 -0500180 }
181
182 /// <summary>
csharptest7fc785c2011-06-10 23:54:53 -0500183 /// Constructs a JsonFormatWriter, use ToString() to extract the final output
csharptestafe844b2011-06-10 16:03:22 -0500184 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500185 public static JsonFormatWriter CreateInstance()
186 {
187 return new JsonTextWriter(null);
188 }
189
csharptest7fc785c2011-06-10 23:54:53 -0500190 /// <summary>
191 /// Constructs a JsonFormatWriter to output to the given text writer
192 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500193 public static JsonFormatWriter CreateInstance(TextWriter output)
194 {
195 return new JsonTextWriter(output);
196 }
csharptest7fc785c2011-06-10 23:54:53 -0500197
198 /// <summary>
199 /// Constructs a JsonFormatWriter to output to the given stream
200 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500201 public static JsonFormatWriter CreateInstance(Stream output)
202 {
203 return new JsonStreamWriter(output);
204 }
csharptest7fc785c2011-06-10 23:54:53 -0500205
206 /// <summary> Write to the output stream </summary>
207 protected void WriteToOutput(string format, params object[] args)
csharptest74c5e0c2011-07-14 13:06:22 -0500208 {
209 WriteToOutput(String.Format(format, args));
210 }
211
csharptest7fc785c2011-06-10 23:54:53 -0500212 /// <summary> Write to the output stream </summary>
213 protected void WriteToOutput(string text)
csharptest74c5e0c2011-07-14 13:06:22 -0500214 {
215 WriteToOutput(text.ToCharArray(), 0, text.Length);
216 }
217
csharptest7fc785c2011-06-10 23:54:53 -0500218 /// <summary> Write to the output stream </summary>
219 protected abstract void WriteToOutput(char ch);
csharptest74c5e0c2011-07-14 13:06:22 -0500220
csharptest7fc785c2011-06-10 23:54:53 -0500221 /// <summary> Write to the output stream </summary>
222 protected abstract void WriteToOutput(char[] chars, int offset, int len);
csharptestafe844b2011-06-10 16:03:22 -0500223
224 /// <summary> Sets the output formatting to use Environment.NewLine with 4-character indentions </summary>
225 public JsonFormatWriter Formatted()
226 {
227 NewLine = Environment.NewLine;
228 Indent = " ";
229 Whitespace = " ";
230 return this;
231 }
232
233 /// <summary> Gets or sets the characters to use for the new-line, default = empty </summary>
234 public string NewLine { get; set; }
csharptest74c5e0c2011-07-14 13:06:22 -0500235
csharptestafe844b2011-06-10 16:03:22 -0500236 /// <summary> Gets or sets the text to use for indenting, default = empty </summary>
237 public string Indent { get; set; }
csharptest74c5e0c2011-07-14 13:06:22 -0500238
csharptestafe844b2011-06-10 16:03:22 -0500239 /// <summary> Gets or sets the whitespace to use to separate the text, default = empty </summary>
240 public string Whitespace { get; set; }
241
242 private void Seperator()
243 {
244 if (_counter.Count == 0)
csharptest74c5e0c2011-07-14 13:06:22 -0500245 {
csharptestafe844b2011-06-10 16:03:22 -0500246 throw new InvalidOperationException("Missmatched open/close in Json writer.");
csharptest74c5e0c2011-07-14 13:06:22 -0500247 }
csharptestafe844b2011-06-10 16:03:22 -0500248
249 int index = _counter.Count - 1;
250 if (_counter[index] > 0)
csharptest74c5e0c2011-07-14 13:06:22 -0500251 {
csharptestafe844b2011-06-10 16:03:22 -0500252 WriteToOutput(',');
csharptest74c5e0c2011-07-14 13:06:22 -0500253 }
csharptestafe844b2011-06-10 16:03:22 -0500254
255 WriteLine(String.Empty);
256 _counter[index] = _counter[index] + 1;
257 }
258
259 private void WriteLine(string content)
260 {
261 if (!String.IsNullOrEmpty(NewLine))
262 {
263 WriteToOutput(NewLine);
264 for (int i = 1; i < _counter.Count; i++)
csharptest74c5e0c2011-07-14 13:06:22 -0500265 {
csharptestafe844b2011-06-10 16:03:22 -0500266 WriteToOutput(Indent);
csharptest74c5e0c2011-07-14 13:06:22 -0500267 }
csharptestafe844b2011-06-10 16:03:22 -0500268 }
csharptest74c5e0c2011-07-14 13:06:22 -0500269 else if (!String.IsNullOrEmpty(Whitespace))
270 {
csharptestafe844b2011-06-10 16:03:22 -0500271 WriteToOutput(Whitespace);
csharptest74c5e0c2011-07-14 13:06:22 -0500272 }
csharptestafe844b2011-06-10 16:03:22 -0500273
274 WriteToOutput(content);
275 }
276
277 private void WriteName(string field)
278 {
279 Seperator();
280 if (!String.IsNullOrEmpty(field))
281 {
282 WriteToOutput('"');
283 WriteToOutput(field);
284 WriteToOutput('"');
285 WriteToOutput(':');
286 if (!String.IsNullOrEmpty(Whitespace))
csharptest74c5e0c2011-07-14 13:06:22 -0500287 {
csharptestafe844b2011-06-10 16:03:22 -0500288 WriteToOutput(Whitespace);
csharptest74c5e0c2011-07-14 13:06:22 -0500289 }
csharptestafe844b2011-06-10 16:03:22 -0500290 }
291 }
292
293 private void EncodeText(string value)
294 {
295 char[] text = value.ToCharArray();
296 int len = text.Length;
297 int pos = 0;
298
299 while (pos < len)
300 {
301 int next = pos;
csharptest74c5e0c2011-07-14 13:06:22 -0500302 while (next < len && text[next] >= 32 && text[next] < 127 && text[next] != '\\' && text[next] != '/' &&
303 text[next] != '"')
304 {
csharptestafe844b2011-06-10 16:03:22 -0500305 next++;
csharptest74c5e0c2011-07-14 13:06:22 -0500306 }
csharptestafe844b2011-06-10 16:03:22 -0500307 WriteToOutput(text, pos, next - pos);
308 if (next < len)
309 {
310 switch (text[next])
311 {
csharptest74c5e0c2011-07-14 13:06:22 -0500312 case '"':
313 WriteToOutput(@"\""");
314 break;
315 case '\\':
316 WriteToOutput(@"\\");
317 break;
318 //odd at best to escape '/', most Json implementations don't, but it is defined in the rfc-4627
319 case '/':
320 WriteToOutput(@"\/");
321 break;
322 case '\b':
323 WriteToOutput(@"\b");
324 break;
325 case '\f':
326 WriteToOutput(@"\f");
327 break;
328 case '\n':
329 WriteToOutput(@"\n");
330 break;
331 case '\r':
332 WriteToOutput(@"\r");
333 break;
334 case '\t':
335 WriteToOutput(@"\t");
336 break;
337 default:
338 WriteToOutput(@"\u{0:x4}", (int) text[next]);
339 break;
csharptestafe844b2011-06-10 16:03:22 -0500340 }
341 next++;
342 }
343 pos = next;
344 }
345 }
346
347 /// <summary>
348 /// Writes a String value
349 /// </summary>
350 protected override void WriteAsText(string field, string textValue, object typedValue)
351 {
352 WriteName(field);
csharptest74c5e0c2011-07-14 13:06:22 -0500353 if (typedValue is bool || typedValue is int || typedValue is uint || typedValue is long ||
354 typedValue is ulong || typedValue is double || typedValue is float)
355 {
csharptestafe844b2011-06-10 16:03:22 -0500356 WriteToOutput(textValue);
csharptest74c5e0c2011-07-14 13:06:22 -0500357 }
csharptestafe844b2011-06-10 16:03:22 -0500358 else
359 {
360 WriteToOutput('"');
361 if (typedValue is string)
csharptest74c5e0c2011-07-14 13:06:22 -0500362 {
csharptestafe844b2011-06-10 16:03:22 -0500363 EncodeText(textValue);
csharptest74c5e0c2011-07-14 13:06:22 -0500364 }
csharptestafe844b2011-06-10 16:03:22 -0500365 else
csharptest74c5e0c2011-07-14 13:06:22 -0500366 {
csharptestafe844b2011-06-10 16:03:22 -0500367 WriteToOutput(textValue);
csharptest74c5e0c2011-07-14 13:06:22 -0500368 }
csharptestafe844b2011-06-10 16:03:22 -0500369 WriteToOutput('"');
370 }
371 }
372
373 /// <summary>
374 /// Writes a Double value
375 /// </summary>
376 protected override void Write(string field, double value)
377 {
378 if (double.IsNaN(value) || double.IsNegativeInfinity(value) || double.IsPositiveInfinity(value))
csharptest74c5e0c2011-07-14 13:06:22 -0500379 {
csharptestafe844b2011-06-10 16:03:22 -0500380 throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity");
csharptest74c5e0c2011-07-14 13:06:22 -0500381 }
csharptestafe844b2011-06-10 16:03:22 -0500382 base.Write(field, value);
383 }
384
385 /// <summary>
386 /// Writes a Single value
387 /// </summary>
388 protected override void Write(string field, float value)
389 {
390 if (float.IsNaN(value) || float.IsNegativeInfinity(value) || float.IsPositiveInfinity(value))
csharptest74c5e0c2011-07-14 13:06:22 -0500391 {
csharptestafe844b2011-06-10 16:03:22 -0500392 throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity");
csharptest74c5e0c2011-07-14 13:06:22 -0500393 }
csharptestafe844b2011-06-10 16:03:22 -0500394 base.Write(field, value);
395 }
396
397 // Treat enum as string
398 protected override void WriteEnum(string field, int number, string name)
399 {
400 Write(field, name);
401 }
402
403 /// <summary>
404 /// Writes an array of field values
405 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500406 protected override void WriteArray(FieldType type, string field, IEnumerable items)
csharptestafe844b2011-06-10 16:03:22 -0500407 {
csharptest74c5e0c2011-07-14 13:06:22 -0500408 IEnumerator enumerator = items.GetEnumerator();
409 try
410 {
411 if (!enumerator.MoveNext())
412 {
413 return;
414 }
415 }
416 finally
417 {
418 if (enumerator is IDisposable)
419 {
420 ((IDisposable) enumerator).Dispose();
421 }
422 }
csharptestafe844b2011-06-10 16:03:22 -0500423
424 WriteName(field);
425 WriteToOutput("[");
426 _counter.Add(0);
427
428 base.WriteArray(type, String.Empty, items);
429
430 _counter.RemoveAt(_counter.Count - 1);
431 WriteLine("]");
432 }
433
434 /// <summary>
435 /// Writes a message
436 /// </summary>
437 protected override void WriteMessageOrGroup(string field, IMessageLite message)
438 {
439 WriteName(field);
440 WriteMessage(message);
441 }
442
443 /// <summary>
444 /// Writes the message to the the formatted stream.
445 /// </summary>
446 public override void WriteMessage(IMessageLite message)
447 {
csharptestc2d2c1a2011-09-08 20:02:11 -0500448 StartMessage();
449 message.WriteTo(this);
450 EndMessage();
451 }
452
453 /// <summary>
454 /// Used to write the root-message preamble, in json this is the left-curly brace '{'.
455 /// After this call you can call IMessageLite.MergeTo(...) and complete the message with
456 /// a call to EndMessage().
457 /// </summary>
458 public override void StartMessage()
459 {
csharptest74c5e0c2011-07-14 13:06:22 -0500460 if (_isArray)
461 {
462 Seperator();
463 }
csharptestafe844b2011-06-10 16:03:22 -0500464 WriteToOutput("{");
465 _counter.Add(0);
csharptestc2d2c1a2011-09-08 20:02:11 -0500466 }
467
468 /// <summary>
469 /// Used to complete a root-message previously started with a call to StartMessage()
470 /// </summary>
471 public override void EndMessage()
472 {
csharptestafe844b2011-06-10 16:03:22 -0500473 _counter.RemoveAt(_counter.Count - 1);
474 WriteLine("}");
475 Flush();
476 }
477
478 /// <summary>
csharptestafe844b2011-06-10 16:03:22 -0500479 /// Used in streaming arrays of objects to the writer
480 /// </summary>
481 /// <example>
482 /// <code>
483 /// using(writer.StartArray())
484 /// foreach(IMessageLite m in messages)
485 /// writer.WriteMessage(m);
486 /// </code>
487 /// </example>
csharptest74c5e0c2011-07-14 13:06:22 -0500488 public sealed class JsonArray : IDisposable
csharptestafe844b2011-06-10 16:03:22 -0500489 {
csharptest74c5e0c2011-07-14 13:06:22 -0500490 private JsonFormatWriter _writer;
491
csharptestafe844b2011-06-10 16:03:22 -0500492 internal JsonArray(JsonFormatWriter writer)
493 {
494 _writer = writer;
495 _writer.WriteToOutput("[");
496 _writer._counter.Add(0);
497 }
498
499 /// <summary>
500 /// Causes the end of the array character to be written.
501 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500502 private void EndArray()
csharptestafe844b2011-06-10 16:03:22 -0500503 {
504 if (_writer != null)
505 {
506 _writer._counter.RemoveAt(_writer._counter.Count - 1);
507 _writer.WriteLine("]");
508 _writer.Flush();
509 }
csharptest74c5e0c2011-07-14 13:06:22 -0500510 _writer = null;
csharptestafe844b2011-06-10 16:03:22 -0500511 }
csharptest74c5e0c2011-07-14 13:06:22 -0500512
513 void IDisposable.Dispose()
514 {
515 EndArray();
516 }
csharptestafe844b2011-06-10 16:03:22 -0500517 }
518
519 /// <summary>
520 /// Used to write an array of messages as the output rather than a single message.
521 /// </summary>
522 /// <example>
523 /// <code>
524 /// using(writer.StartArray())
525 /// foreach(IMessageLite m in messages)
526 /// writer.WriteMessage(m);
527 /// </code>
528 /// </example>
529 public JsonArray StartArray()
530 {
csharptest74c5e0c2011-07-14 13:06:22 -0500531 if (_isArray)
532 {
533 Seperator();
534 }
csharptestafe844b2011-06-10 16:03:22 -0500535 _isArray = true;
536 return new JsonArray(this);
537 }
538 }
539}