aboutsummaryrefslogtreecommitdiff
path: root/csharp/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs
diff options
context:
space:
mode:
Diffstat (limited to 'csharp/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs')
-rw-r--r--csharp/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs541
1 files changed, 541 insertions, 0 deletions
diff --git a/csharp/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs b/csharp/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs
new file mode 100644
index 00000000..15e0424e
--- /dev/null
+++ b/csharp/src/ProtocolBuffers.Serialization/JsonFormatWriter.cs
@@ -0,0 +1,541 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.Serialization
+{
+ /// <summary>
+ /// JsonFormatWriter is a .NET 2.0 friendly json formatter for proto buffer messages. For .NET 3.5
+ /// you may also use the XmlFormatWriter with an XmlWriter created by the
+ /// <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory">JsonReaderWriterFactory</see>.
+ /// </summary>
+ public abstract class JsonFormatWriter : AbstractTextWriter
+ {
+ #region buffering implementations
+
+ private class JsonTextWriter : JsonFormatWriter
+ {
+ private readonly char[] _buffer;
+ private TextWriter _output;
+ private int _bufferPos;
+
+ public JsonTextWriter(TextWriter output)
+ {
+ _buffer = new char[4096];
+ _bufferPos = 0;
+ _output = output;
+ _counter.Add(0);
+ }
+
+ /// <summary>
+ /// Returns the output of TextWriter.ToString() where TextWriter is the ctor argument.
+ /// </summary>
+ public override string ToString()
+ {
+ Flush();
+
+ if (_output != null)
+ {
+ return _output.ToString();
+ }
+
+ return new String(_buffer, 0, _bufferPos);
+ }
+
+ protected override void WriteToOutput(char[] chars, int offset, int len)
+ {
+ if (_bufferPos + len >= _buffer.Length)
+ {
+ if (_output == null)
+ {
+ _output = new StringWriter(new StringBuilder(_buffer.Length*2 + len));
+ }
+ Flush();
+ }
+
+ if (len < _buffer.Length)
+ {
+ if (len <= 12)
+ {
+ int stop = offset + len;
+ for (int i = offset; i < stop; i++)
+ {
+ _buffer[_bufferPos++] = chars[i];
+ }
+ }
+ else
+ {
+ Buffer.BlockCopy(chars, offset << 1, _buffer, _bufferPos << 1, len << 1);
+ _bufferPos += len;
+ }
+ }
+ else
+ {
+ _output.Write(chars, offset, len);
+ }
+ }
+
+ protected override void WriteToOutput(char ch)
+ {
+ if (_bufferPos >= _buffer.Length)
+ {
+ if (_output == null)
+ {
+ _output = new StringWriter(new StringBuilder(_buffer.Length * 2));
+ }
+ Flush();
+ }
+ _buffer[_bufferPos++] = ch;
+ }
+
+ public override void Flush()
+ {
+ if (_bufferPos > 0 && _output != null)
+ {
+ _output.Write(_buffer, 0, _bufferPos);
+ _bufferPos = 0;
+ }
+ base.Flush();
+ }
+ }
+
+ private class JsonStreamWriter : JsonFormatWriter
+ {
+ static readonly Encoding Encoding = new UTF8Encoding(false);
+ private readonly byte[] _buffer;
+ private Stream _output;
+ private int _bufferPos;
+
+ public JsonStreamWriter(Stream output)
+ {
+ _buffer = new byte[8192];
+ _bufferPos = 0;
+ _output = output;
+ _counter.Add(0);
+ }
+
+ protected override void WriteToOutput(char[] chars, int offset, int len)
+ {
+ if (_bufferPos + len >= _buffer.Length)
+ {
+ Flush();
+ }
+
+ if (len < _buffer.Length)
+ {
+ if (len <= 12)
+ {
+ int stop = offset + len;
+ for (int i = offset; i < stop; i++)
+ {
+ _buffer[_bufferPos++] = (byte) chars[i];
+ }
+ }
+ else
+ {
+ _bufferPos += Encoding.GetBytes(chars, offset, len, _buffer, _bufferPos);
+ }
+ }
+ else
+ {
+ byte[] temp = Encoding.GetBytes(chars, offset, len);
+ _output.Write(temp, 0, temp.Length);
+ }
+ }
+
+ protected override void WriteToOutput(char ch)
+ {
+ if (_bufferPos >= _buffer.Length)
+ {
+ Flush();
+ }
+ _buffer[_bufferPos++] = (byte) ch;
+ }
+
+ public override void Flush()
+ {
+ if (_bufferPos > 0 && _output != null)
+ {
+ _output.Write(_buffer, 0, _bufferPos);
+ _bufferPos = 0;
+ }
+ base.Flush();
+ }
+ }
+
+ #endregion
+
+ //Tracks the writer depth and the array element count at that depth.
+ private readonly List<int> _counter;
+ //True if the top-level of the writer is an array as opposed to a single message.
+ private bool _isArray;
+
+ /// <summary>
+ /// Constructs a JsonFormatWriter, use the ToString() member to extract the final Json on completion.
+ /// </summary>
+ protected JsonFormatWriter()
+ {
+ _counter = new List<int>();
+ }
+
+ /// <summary>
+ /// Constructs a JsonFormatWriter, use ToString() to extract the final output
+ /// </summary>
+ public static JsonFormatWriter CreateInstance()
+ {
+ return new JsonTextWriter(null);
+ }
+
+ /// <summary>
+ /// Constructs a JsonFormatWriter to output to the given text writer
+ /// </summary>
+ public static JsonFormatWriter CreateInstance(TextWriter output)
+ {
+ return new JsonTextWriter(output);
+ }
+
+ /// <summary>
+ /// Constructs a JsonFormatWriter to output to the given stream
+ /// </summary>
+ public static JsonFormatWriter CreateInstance(Stream output)
+ {
+ return new JsonStreamWriter(output);
+ }
+
+ /// <summary> Write to the output stream </summary>
+ protected void WriteToOutput(string format, params object[] args)
+ {
+ WriteToOutput(String.Format(format, args));
+ }
+
+ /// <summary> Write to the output stream </summary>
+ protected void WriteToOutput(string text)
+ {
+ WriteToOutput(text.ToCharArray(), 0, text.Length);
+ }
+
+ /// <summary> Write to the output stream </summary>
+ protected abstract void WriteToOutput(char ch);
+
+ /// <summary> Write to the output stream </summary>
+ protected abstract void WriteToOutput(char[] chars, int offset, int len);
+
+ /// <summary> Sets the output formatting to use Environment.NewLine with 4-character indentions </summary>
+ public JsonFormatWriter Formatted()
+ {
+ NewLine = FrameworkPortability.NewLine;
+ Indent = " ";
+ Whitespace = " ";
+ return this;
+ }
+
+ /// <summary> Gets or sets the characters to use for the new-line, default = empty </summary>
+ public string NewLine { get; set; }
+
+ /// <summary> Gets or sets the text to use for indenting, default = empty </summary>
+ public string Indent { get; set; }
+
+ /// <summary> Gets or sets the whitespace to use to separate the text, default = empty </summary>
+ public string Whitespace { get; set; }
+
+ private void Seperator()
+ {
+ if (_counter.Count == 0)
+ {
+ throw new InvalidOperationException("Mismatched open/close in Json writer.");
+ }
+
+ int index = _counter.Count - 1;
+ if (_counter[index] > 0)
+ {
+ WriteToOutput(',');
+ }
+
+ WriteLine(String.Empty);
+ _counter[index] = _counter[index] + 1;
+ }
+
+ private void WriteLine(string content)
+ {
+ if (!String.IsNullOrEmpty(NewLine))
+ {
+ WriteToOutput(NewLine);
+ for (int i = 1; i < _counter.Count; i++)
+ {
+ WriteToOutput(Indent);
+ }
+ }
+ else if (!String.IsNullOrEmpty(Whitespace))
+ {
+ WriteToOutput(Whitespace);
+ }
+
+ WriteToOutput(content);
+ }
+
+ private void WriteName(string field)
+ {
+ Seperator();
+ if (!String.IsNullOrEmpty(field))
+ {
+ WriteToOutput('"');
+ WriteToOutput(field);
+ WriteToOutput('"');
+ WriteToOutput(':');
+ if (!String.IsNullOrEmpty(Whitespace))
+ {
+ WriteToOutput(Whitespace);
+ }
+ }
+ }
+
+ private void EncodeText(string value)
+ {
+ char[] text = value.ToCharArray();
+ int len = text.Length;
+ int pos = 0;
+
+ while (pos < len)
+ {
+ int next = pos;
+ while (next < len && text[next] >= 32 && text[next] < 127 && text[next] != '\\' && text[next] != '/' &&
+ text[next] != '"')
+ {
+ next++;
+ }
+ WriteToOutput(text, pos, next - pos);
+ if (next < len)
+ {
+ switch (text[next])
+ {
+ case '"':
+ WriteToOutput(@"\""");
+ break;
+ case '\\':
+ WriteToOutput(@"\\");
+ break;
+ //odd at best to escape '/', most Json implementations don't, but it is defined in the rfc-4627
+ case '/':
+ WriteToOutput(@"\/");
+ break;
+ case '\b':
+ WriteToOutput(@"\b");
+ break;
+ case '\f':
+ WriteToOutput(@"\f");
+ break;
+ case '\n':
+ WriteToOutput(@"\n");
+ break;
+ case '\r':
+ WriteToOutput(@"\r");
+ break;
+ case '\t':
+ WriteToOutput(@"\t");
+ break;
+ default:
+ WriteToOutput(@"\u{0:x4}", (int) text[next]);
+ break;
+ }
+ next++;
+ }
+ pos = next;
+ }
+ }
+
+ /// <summary>
+ /// Writes a String value
+ /// </summary>
+ protected override void WriteAsText(string field, string textValue, object typedValue)
+ {
+ WriteName(field);
+ if (typedValue is bool || typedValue is int || typedValue is uint || typedValue is long ||
+ typedValue is ulong || typedValue is double || typedValue is float)
+ {
+ WriteToOutput(textValue);
+ }
+ else
+ {
+ WriteToOutput('"');
+ if (typedValue is string)
+ {
+ EncodeText(textValue);
+ }
+ else
+ {
+ WriteToOutput(textValue);
+ }
+ WriteToOutput('"');
+ }
+ }
+
+ /// <summary>
+ /// Writes a Double value
+ /// </summary>
+ protected override void Write(string field, double value)
+ {
+ if (double.IsNaN(value) || double.IsNegativeInfinity(value) || double.IsPositiveInfinity(value))
+ {
+ throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity");
+ }
+ base.Write(field, value);
+ }
+
+ /// <summary>
+ /// Writes a Single value
+ /// </summary>
+ protected override void Write(string field, float value)
+ {
+ if (float.IsNaN(value) || float.IsNegativeInfinity(value) || float.IsPositiveInfinity(value))
+ {
+ throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity");
+ }
+ base.Write(field, value);
+ }
+
+ // Treat enum as string
+ protected override void WriteEnum(string field, int number, string name)
+ {
+ Write(field, name);
+ }
+
+ /// <summary>
+ /// Writes an array of field values
+ /// </summary>
+ protected override void WriteArray(FieldType type, string field, IEnumerable items)
+ {
+ IEnumerator enumerator = items.GetEnumerator();
+ try
+ {
+ if (!enumerator.MoveNext())
+ {
+ return;
+ }
+ }
+ finally
+ {
+ if (enumerator is IDisposable)
+ {
+ ((IDisposable) enumerator).Dispose();
+ }
+ }
+
+ WriteName(field);
+ WriteToOutput("[");
+ _counter.Add(0);
+
+ base.WriteArray(type, String.Empty, items);
+
+ _counter.RemoveAt(_counter.Count - 1);
+ WriteLine("]");
+ }
+
+ /// <summary>
+ /// Writes a message
+ /// </summary>
+ protected override void WriteMessageOrGroup(string field, IMessageLite message)
+ {
+ WriteName(field);
+ WriteMessage(message);
+ }
+
+ /// <summary>
+ /// Writes the message to the the formatted stream.
+ /// </summary>
+ public override void WriteMessage(IMessageLite message)
+ {
+ WriteMessageStart();
+ message.WriteTo(this);
+ WriteMessageEnd();
+ }
+
+ /// <summary>
+ /// Used to write the root-message preamble, in json this is the left-curly brace '{'.
+ /// After this call you can call IMessageLite.MergeTo(...) and complete the message with
+ /// a call to WriteMessageEnd().
+ /// </summary>
+ public override void WriteMessageStart()
+ {
+ if (_isArray)
+ {
+ Seperator();
+ }
+ WriteToOutput("{");
+ _counter.Add(0);
+ }
+
+ /// <summary>
+ /// Used to complete a root-message previously started with a call to WriteMessageStart()
+ /// </summary>
+ public override void WriteMessageEnd()
+ {
+ _counter.RemoveAt(_counter.Count - 1);
+ WriteLine("}");
+ Flush();
+ }
+
+ /// <summary>
+ /// Used in streaming arrays of objects to the writer
+ /// </summary>
+ /// <example>
+ /// <code>
+ /// using(writer.StartArray())
+ /// foreach(IMessageLite m in messages)
+ /// writer.WriteMessage(m);
+ /// </code>
+ /// </example>
+ public sealed class JsonArray : IDisposable
+ {
+ private JsonFormatWriter _writer;
+
+ internal JsonArray(JsonFormatWriter writer)
+ {
+ _writer = writer;
+ _writer.WriteToOutput("[");
+ _writer._counter.Add(0);
+ }
+
+ /// <summary>
+ /// Causes the end of the array character to be written.
+ /// </summary>
+ private void EndArray()
+ {
+ if (_writer != null)
+ {
+ _writer._counter.RemoveAt(_writer._counter.Count - 1);
+ _writer.WriteLine("]");
+ _writer.Flush();
+ }
+ _writer = null;
+ }
+
+ void IDisposable.Dispose()
+ {
+ EndArray();
+ }
+ }
+
+ /// <summary>
+ /// Used to write an array of messages as the output rather than a single message.
+ /// </summary>
+ /// <example>
+ /// <code>
+ /// using(writer.StartArray())
+ /// foreach(IMessageLite m in messages)
+ /// writer.WriteMessage(m);
+ /// </code>
+ /// </example>
+ public JsonArray StartArray()
+ {
+ if (_isArray)
+ {
+ Seperator();
+ }
+ _isArray = true;
+ return new JsonArray(this);
+ }
+ }
+} \ No newline at end of file