csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 1 | using System;
|
| 2 | using System.Collections.Generic;
|
| 3 | using System.IO;
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame^] | 4 | using System.Text;
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 5 | using Google.ProtocolBuffers.Descriptors;
|
| 6 |
|
| 7 | namespace 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>
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame^] | 14 | public abstract class JsonFormatWriter : AbstractTextWriter
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 15 | {
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame^] | 16 | #region buffering implementations
|
| 17 | private class JsonTextWriter : JsonFormatWriter
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 18 | {
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame^] | 19 | private readonly char[] _buffer;
|
| 20 | private TextWriter _output;
|
| 21 | int _bufferPos;
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 22 |
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame^] | 23 | public JsonTextWriter(TextWriter output)
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 24 | {
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame^] | 25 | _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)
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 47 | {
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame^] | 48 | 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 | }
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 124 | }
|
| 125 | else
|
| 126 | {
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame^] | 127 | byte[] temp = Encoding.GetBytes(chars, offset, len);
|
| 128 | _output.Write(temp, 0, temp.Length);
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 129 | }
|
| 130 | }
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 131 |
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame^] | 132 | protected override void WriteToOutput(char ch)
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 133 | {
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame^] | 134 | if (_bufferPos >= _buffer.Length)
|
| 135 | Flush();
|
| 136 | _buffer[_bufferPos++] = (byte)ch;
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 137 | }
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame^] | 138 |
|
| 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>();
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 159 | }
|
| 160 |
|
| 161 | /// <summary>
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame^] | 162 | /// Constructs a JsonFormatWriter, use ToString() to extract the final output
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 163 | /// </summary>
|
csharptest | 7fc785c | 2011-06-10 23:54:53 -0500 | [diff] [blame^] | 164 | 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);
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 186 |
|
| 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>
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 365 | /// 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 | } |