blob: 5f396ae58f6b6b42cc5c0f572de0cf6620ce9759 [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
csharptest819b7152011-09-08 20:28:22 -0500242 /// <summary>
243 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
244 /// </summary>
245 protected override void Dispose(bool disposing)
246 {
247 if (disposing && _counter.Count == 1)
248 {
249 EndMessage();
250 }
251
252 base.Dispose(disposing);
253 }
254
csharptestafe844b2011-06-10 16:03:22 -0500255 private void Seperator()
256 {
257 if (_counter.Count == 0)
csharptest74c5e0c2011-07-14 13:06:22 -0500258 {
csharptestafe844b2011-06-10 16:03:22 -0500259 throw new InvalidOperationException("Missmatched open/close in Json writer.");
csharptest74c5e0c2011-07-14 13:06:22 -0500260 }
csharptestafe844b2011-06-10 16:03:22 -0500261
262 int index = _counter.Count - 1;
263 if (_counter[index] > 0)
csharptest74c5e0c2011-07-14 13:06:22 -0500264 {
csharptestafe844b2011-06-10 16:03:22 -0500265 WriteToOutput(',');
csharptest74c5e0c2011-07-14 13:06:22 -0500266 }
csharptestafe844b2011-06-10 16:03:22 -0500267
268 WriteLine(String.Empty);
269 _counter[index] = _counter[index] + 1;
270 }
271
272 private void WriteLine(string content)
273 {
274 if (!String.IsNullOrEmpty(NewLine))
275 {
276 WriteToOutput(NewLine);
277 for (int i = 1; i < _counter.Count; i++)
csharptest74c5e0c2011-07-14 13:06:22 -0500278 {
csharptestafe844b2011-06-10 16:03:22 -0500279 WriteToOutput(Indent);
csharptest74c5e0c2011-07-14 13:06:22 -0500280 }
csharptestafe844b2011-06-10 16:03:22 -0500281 }
csharptest74c5e0c2011-07-14 13:06:22 -0500282 else if (!String.IsNullOrEmpty(Whitespace))
283 {
csharptestafe844b2011-06-10 16:03:22 -0500284 WriteToOutput(Whitespace);
csharptest74c5e0c2011-07-14 13:06:22 -0500285 }
csharptestafe844b2011-06-10 16:03:22 -0500286
287 WriteToOutput(content);
288 }
289
290 private void WriteName(string field)
291 {
292 Seperator();
293 if (!String.IsNullOrEmpty(field))
294 {
295 WriteToOutput('"');
296 WriteToOutput(field);
297 WriteToOutput('"');
298 WriteToOutput(':');
299 if (!String.IsNullOrEmpty(Whitespace))
csharptest74c5e0c2011-07-14 13:06:22 -0500300 {
csharptestafe844b2011-06-10 16:03:22 -0500301 WriteToOutput(Whitespace);
csharptest74c5e0c2011-07-14 13:06:22 -0500302 }
csharptestafe844b2011-06-10 16:03:22 -0500303 }
304 }
305
306 private void EncodeText(string value)
307 {
308 char[] text = value.ToCharArray();
309 int len = text.Length;
310 int pos = 0;
311
312 while (pos < len)
313 {
314 int next = pos;
csharptest74c5e0c2011-07-14 13:06:22 -0500315 while (next < len && text[next] >= 32 && text[next] < 127 && text[next] != '\\' && text[next] != '/' &&
316 text[next] != '"')
317 {
csharptestafe844b2011-06-10 16:03:22 -0500318 next++;
csharptest74c5e0c2011-07-14 13:06:22 -0500319 }
csharptestafe844b2011-06-10 16:03:22 -0500320 WriteToOutput(text, pos, next - pos);
321 if (next < len)
322 {
323 switch (text[next])
324 {
csharptest74c5e0c2011-07-14 13:06:22 -0500325 case '"':
326 WriteToOutput(@"\""");
327 break;
328 case '\\':
329 WriteToOutput(@"\\");
330 break;
331 //odd at best to escape '/', most Json implementations don't, but it is defined in the rfc-4627
332 case '/':
333 WriteToOutput(@"\/");
334 break;
335 case '\b':
336 WriteToOutput(@"\b");
337 break;
338 case '\f':
339 WriteToOutput(@"\f");
340 break;
341 case '\n':
342 WriteToOutput(@"\n");
343 break;
344 case '\r':
345 WriteToOutput(@"\r");
346 break;
347 case '\t':
348 WriteToOutput(@"\t");
349 break;
350 default:
351 WriteToOutput(@"\u{0:x4}", (int) text[next]);
352 break;
csharptestafe844b2011-06-10 16:03:22 -0500353 }
354 next++;
355 }
356 pos = next;
357 }
358 }
359
360 /// <summary>
361 /// Writes a String value
362 /// </summary>
363 protected override void WriteAsText(string field, string textValue, object typedValue)
364 {
365 WriteName(field);
csharptest74c5e0c2011-07-14 13:06:22 -0500366 if (typedValue is bool || typedValue is int || typedValue is uint || typedValue is long ||
367 typedValue is ulong || typedValue is double || typedValue is float)
368 {
csharptestafe844b2011-06-10 16:03:22 -0500369 WriteToOutput(textValue);
csharptest74c5e0c2011-07-14 13:06:22 -0500370 }
csharptestafe844b2011-06-10 16:03:22 -0500371 else
372 {
373 WriteToOutput('"');
374 if (typedValue is string)
csharptest74c5e0c2011-07-14 13:06:22 -0500375 {
csharptestafe844b2011-06-10 16:03:22 -0500376 EncodeText(textValue);
csharptest74c5e0c2011-07-14 13:06:22 -0500377 }
csharptestafe844b2011-06-10 16:03:22 -0500378 else
csharptest74c5e0c2011-07-14 13:06:22 -0500379 {
csharptestafe844b2011-06-10 16:03:22 -0500380 WriteToOutput(textValue);
csharptest74c5e0c2011-07-14 13:06:22 -0500381 }
csharptestafe844b2011-06-10 16:03:22 -0500382 WriteToOutput('"');
383 }
384 }
385
386 /// <summary>
387 /// Writes a Double value
388 /// </summary>
389 protected override void Write(string field, double value)
390 {
391 if (double.IsNaN(value) || double.IsNegativeInfinity(value) || double.IsPositiveInfinity(value))
csharptest74c5e0c2011-07-14 13:06:22 -0500392 {
csharptestafe844b2011-06-10 16:03:22 -0500393 throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity");
csharptest74c5e0c2011-07-14 13:06:22 -0500394 }
csharptestafe844b2011-06-10 16:03:22 -0500395 base.Write(field, value);
396 }
397
398 /// <summary>
399 /// Writes a Single value
400 /// </summary>
401 protected override void Write(string field, float value)
402 {
403 if (float.IsNaN(value) || float.IsNegativeInfinity(value) || float.IsPositiveInfinity(value))
csharptest74c5e0c2011-07-14 13:06:22 -0500404 {
csharptestafe844b2011-06-10 16:03:22 -0500405 throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity");
csharptest74c5e0c2011-07-14 13:06:22 -0500406 }
csharptestafe844b2011-06-10 16:03:22 -0500407 base.Write(field, value);
408 }
409
410 // Treat enum as string
411 protected override void WriteEnum(string field, int number, string name)
412 {
413 Write(field, name);
414 }
415
416 /// <summary>
417 /// Writes an array of field values
418 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500419 protected override void WriteArray(FieldType type, string field, IEnumerable items)
csharptestafe844b2011-06-10 16:03:22 -0500420 {
csharptest74c5e0c2011-07-14 13:06:22 -0500421 IEnumerator enumerator = items.GetEnumerator();
422 try
423 {
424 if (!enumerator.MoveNext())
425 {
426 return;
427 }
428 }
429 finally
430 {
431 if (enumerator is IDisposable)
432 {
433 ((IDisposable) enumerator).Dispose();
434 }
435 }
csharptestafe844b2011-06-10 16:03:22 -0500436
437 WriteName(field);
438 WriteToOutput("[");
439 _counter.Add(0);
440
441 base.WriteArray(type, String.Empty, items);
442
443 _counter.RemoveAt(_counter.Count - 1);
444 WriteLine("]");
445 }
446
447 /// <summary>
448 /// Writes a message
449 /// </summary>
450 protected override void WriteMessageOrGroup(string field, IMessageLite message)
451 {
452 WriteName(field);
453 WriteMessage(message);
454 }
455
456 /// <summary>
457 /// Writes the message to the the formatted stream.
458 /// </summary>
459 public override void WriteMessage(IMessageLite message)
460 {
csharptestc2d2c1a2011-09-08 20:02:11 -0500461 StartMessage();
462 message.WriteTo(this);
463 EndMessage();
464 }
465
466 /// <summary>
467 /// Used to write the root-message preamble, in json this is the left-curly brace '{'.
468 /// After this call you can call IMessageLite.MergeTo(...) and complete the message with
469 /// a call to EndMessage().
470 /// </summary>
471 public override void StartMessage()
472 {
csharptest74c5e0c2011-07-14 13:06:22 -0500473 if (_isArray)
474 {
475 Seperator();
476 }
csharptestafe844b2011-06-10 16:03:22 -0500477 WriteToOutput("{");
478 _counter.Add(0);
csharptestc2d2c1a2011-09-08 20:02:11 -0500479 }
480
481 /// <summary>
482 /// Used to complete a root-message previously started with a call to StartMessage()
483 /// </summary>
484 public override void EndMessage()
485 {
csharptestafe844b2011-06-10 16:03:22 -0500486 _counter.RemoveAt(_counter.Count - 1);
487 WriteLine("}");
488 Flush();
489 }
490
491 /// <summary>
csharptestafe844b2011-06-10 16:03:22 -0500492 /// Used in streaming arrays of objects to the writer
493 /// </summary>
494 /// <example>
495 /// <code>
496 /// using(writer.StartArray())
497 /// foreach(IMessageLite m in messages)
498 /// writer.WriteMessage(m);
499 /// </code>
500 /// </example>
csharptest74c5e0c2011-07-14 13:06:22 -0500501 public sealed class JsonArray : IDisposable
csharptestafe844b2011-06-10 16:03:22 -0500502 {
csharptest74c5e0c2011-07-14 13:06:22 -0500503 private JsonFormatWriter _writer;
504
csharptestafe844b2011-06-10 16:03:22 -0500505 internal JsonArray(JsonFormatWriter writer)
506 {
507 _writer = writer;
508 _writer.WriteToOutput("[");
509 _writer._counter.Add(0);
510 }
511
512 /// <summary>
513 /// Causes the end of the array character to be written.
514 /// </summary>
csharptest74c5e0c2011-07-14 13:06:22 -0500515 private void EndArray()
csharptestafe844b2011-06-10 16:03:22 -0500516 {
517 if (_writer != null)
518 {
519 _writer._counter.RemoveAt(_writer._counter.Count - 1);
520 _writer.WriteLine("]");
521 _writer.Flush();
522 }
csharptest74c5e0c2011-07-14 13:06:22 -0500523 _writer = null;
csharptestafe844b2011-06-10 16:03:22 -0500524 }
csharptest74c5e0c2011-07-14 13:06:22 -0500525
526 void IDisposable.Dispose()
527 {
528 EndArray();
529 }
csharptestafe844b2011-06-10 16:03:22 -0500530 }
531
532 /// <summary>
533 /// Used to write an array of messages as the output rather than a single message.
534 /// </summary>
535 /// <example>
536 /// <code>
537 /// using(writer.StartArray())
538 /// foreach(IMessageLite m in messages)
539 /// writer.WriteMessage(m);
540 /// </code>
541 /// </example>
542 public JsonArray StartArray()
543 {
csharptest74c5e0c2011-07-14 13:06:22 -0500544 if (_isArray)
545 {
546 Seperator();
547 }
csharptestafe844b2011-06-10 16:03:22 -0500548 _isArray = true;
549 return new JsonArray(this);
550 }
551 }
552}