csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 1 | using System;
|
| 2 | using System.Collections.Generic;
|
| 3 | using System.IO;
|
| 4 | using Google.ProtocolBuffers.Descriptors;
|
| 5 |
|
| 6 | namespace Google.ProtocolBuffers.Serialization
|
| 7 | {
|
| 8 | /// <summary>
|
| 9 | /// JsonFormatWriter is a .NET 2.0 friendly json formatter for proto buffer messages. For .NET 3.5
|
| 10 | /// you may also use the XmlFormatWriter with an XmlWriter created by the
|
| 11 | /// <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory">JsonReaderWriterFactory</see>.
|
| 12 | /// </summary>
|
| 13 | public class JsonFormatWriter : AbstractTextWriter
|
| 14 | {
|
| 15 | private readonly char[] _buffer;
|
| 16 | private readonly TextWriter _output;
|
| 17 | private readonly List<int> _counter;
|
| 18 | private bool _isArray;
|
| 19 | int _bufferPos;
|
| 20 | /// <summary>
|
| 21 | /// Constructs a JsonFormatWriter to output to a new instance of a StringWriter, use
|
| 22 | /// the ToString() member to extract the final Json on completion.
|
| 23 | /// </summary>
|
| 24 | public JsonFormatWriter() : this(new StringWriter()) { }
|
| 25 | /// <summary>
|
| 26 | /// Constructs a JsonFormatWriter to output to the given text writer
|
| 27 | /// </summary>
|
| 28 | public JsonFormatWriter(TextWriter output)
|
| 29 | {
|
| 30 | _buffer = new char[4096];
|
| 31 | _bufferPos = 0;
|
| 32 | _output = output;
|
| 33 | _counter = new List<int>();
|
| 34 | _counter.Add(0);
|
| 35 | }
|
| 36 |
|
| 37 |
|
| 38 | private void WriteToOutput(string format, params object[] args)
|
| 39 | { WriteToOutput(String.Format(format, args)); }
|
| 40 |
|
| 41 | private void WriteToOutput(string text)
|
| 42 | { WriteToOutput(text.ToCharArray(), 0, text.Length); }
|
| 43 |
|
| 44 | private void WriteToOutput(char[] chars, int offset, int len)
|
| 45 | {
|
| 46 | if (_bufferPos + len >= _buffer.Length)
|
| 47 | Flush();
|
| 48 | if (len < _buffer.Length)
|
| 49 | {
|
| 50 | if (len <= 12)
|
| 51 | {
|
| 52 | int stop = offset + len;
|
| 53 | for (int i = offset; i < stop; i++)
|
| 54 | _buffer[_bufferPos++] = chars[i];
|
| 55 | }
|
| 56 | else
|
| 57 | {
|
| 58 | Buffer.BlockCopy(chars, offset << 1, _buffer, _bufferPos << 1, len << 1);
|
| 59 | _bufferPos += len;
|
| 60 | }
|
| 61 | }
|
| 62 | else
|
| 63 | _output.Write(chars, offset, len);
|
| 64 | }
|
| 65 |
|
| 66 | private void WriteToOutput(char ch)
|
| 67 | {
|
| 68 | if (_bufferPos >= _buffer.Length)
|
| 69 | Flush();
|
| 70 | _buffer[_bufferPos++] = ch;
|
| 71 | }
|
| 72 |
|
| 73 | public override void Flush()
|
| 74 | {
|
| 75 | if (_bufferPos > 0)
|
| 76 | {
|
| 77 | _output.Write(_buffer, 0, _bufferPos);
|
| 78 | _bufferPos = 0;
|
| 79 | }
|
| 80 | base.Flush();
|
| 81 | }
|
| 82 |
|
| 83 | /// <summary>
|
| 84 | /// Returns the output of TextWriter.ToString() where TextWriter is the ctor argument.
|
| 85 | /// </summary>
|
| 86 | public override string ToString()
|
| 87 | { Flush(); return _output.ToString(); }
|
| 88 |
|
| 89 | /// <summary> Sets the output formatting to use Environment.NewLine with 4-character indentions </summary>
|
| 90 | public JsonFormatWriter Formatted()
|
| 91 | {
|
| 92 | NewLine = Environment.NewLine;
|
| 93 | Indent = " ";
|
| 94 | Whitespace = " ";
|
| 95 | return this;
|
| 96 | }
|
| 97 |
|
| 98 | /// <summary> Gets or sets the characters to use for the new-line, default = empty </summary>
|
| 99 | public string NewLine { get; set; }
|
| 100 | /// <summary> Gets or sets the text to use for indenting, default = empty </summary>
|
| 101 | public string Indent { get; set; }
|
| 102 | /// <summary> Gets or sets the whitespace to use to separate the text, default = empty </summary>
|
| 103 | public string Whitespace { get; set; }
|
| 104 |
|
| 105 | private void Seperator()
|
| 106 | {
|
| 107 | if (_counter.Count == 0)
|
| 108 | throw new InvalidOperationException("Missmatched open/close in Json writer.");
|
| 109 |
|
| 110 | int index = _counter.Count - 1;
|
| 111 | if (_counter[index] > 0)
|
| 112 | WriteToOutput(',');
|
| 113 |
|
| 114 | WriteLine(String.Empty);
|
| 115 | _counter[index] = _counter[index] + 1;
|
| 116 | }
|
| 117 |
|
| 118 | private void WriteLine(string content)
|
| 119 | {
|
| 120 | if (!String.IsNullOrEmpty(NewLine))
|
| 121 | {
|
| 122 | WriteToOutput(NewLine);
|
| 123 | for (int i = 1; i < _counter.Count; i++)
|
| 124 | WriteToOutput(Indent);
|
| 125 | }
|
| 126 | else if(!String.IsNullOrEmpty(Whitespace))
|
| 127 | WriteToOutput(Whitespace);
|
| 128 |
|
| 129 | WriteToOutput(content);
|
| 130 | }
|
| 131 |
|
| 132 | private void WriteName(string field)
|
| 133 | {
|
| 134 | Seperator();
|
| 135 | if (!String.IsNullOrEmpty(field))
|
| 136 | {
|
| 137 | WriteToOutput('"');
|
| 138 | WriteToOutput(field);
|
| 139 | WriteToOutput('"');
|
| 140 | WriteToOutput(':');
|
| 141 | if (!String.IsNullOrEmpty(Whitespace))
|
| 142 | WriteToOutput(Whitespace);
|
| 143 | }
|
| 144 | }
|
| 145 |
|
| 146 | private void EncodeText(string value)
|
| 147 | {
|
| 148 | char[] text = value.ToCharArray();
|
| 149 | int len = text.Length;
|
| 150 | int pos = 0;
|
| 151 |
|
| 152 | while (pos < len)
|
| 153 | {
|
| 154 | int next = pos;
|
| 155 | while (next < len && text[next] >= 32 && text[next] < 127 && text[next] != '\\' && text[next] != '/' && text[next] != '"')
|
| 156 | next++;
|
| 157 | WriteToOutput(text, pos, next - pos);
|
| 158 | if (next < len)
|
| 159 | {
|
| 160 | switch (text[next])
|
| 161 | {
|
| 162 | case '"': WriteToOutput(@"\"""); break;
|
| 163 | case '\\': WriteToOutput(@"\\"); break;
|
| 164 | //odd at best to escape '/', most Json implementations don't, but it is defined in the rfc-4627
|
| 165 | case '/': WriteToOutput(@"\/"); break;
|
| 166 | case '\b': WriteToOutput(@"\b"); break;
|
| 167 | case '\f': WriteToOutput(@"\f"); break;
|
| 168 | case '\n': WriteToOutput(@"\n"); break;
|
| 169 | case '\r': WriteToOutput(@"\r"); break;
|
| 170 | case '\t': WriteToOutput(@"\t"); break;
|
| 171 | default: WriteToOutput(@"\u{0:x4}", (int)text[next]); break;
|
| 172 | }
|
| 173 | next++;
|
| 174 | }
|
| 175 | pos = next;
|
| 176 | }
|
| 177 | }
|
| 178 |
|
| 179 | /// <summary>
|
| 180 | /// Writes a String value
|
| 181 | /// </summary>
|
| 182 | protected override void WriteAsText(string field, string textValue, object typedValue)
|
| 183 | {
|
| 184 | WriteName(field);
|
| 185 | if(typedValue is bool || typedValue is int || typedValue is uint || typedValue is long || typedValue is ulong || typedValue is double || typedValue is float)
|
| 186 | WriteToOutput(textValue);
|
| 187 | else
|
| 188 | {
|
| 189 | WriteToOutput('"');
|
| 190 | if (typedValue is string)
|
| 191 | EncodeText(textValue);
|
| 192 | else
|
| 193 | WriteToOutput(textValue);
|
| 194 | WriteToOutput('"');
|
| 195 | }
|
| 196 | }
|
| 197 |
|
| 198 | /// <summary>
|
| 199 | /// Writes a Double value
|
| 200 | /// </summary>
|
| 201 | protected override void Write(string field, double value)
|
| 202 | {
|
| 203 | if (double.IsNaN(value) || double.IsNegativeInfinity(value) || double.IsPositiveInfinity(value))
|
| 204 | throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity");
|
| 205 | base.Write(field, value);
|
| 206 | }
|
| 207 |
|
| 208 | /// <summary>
|
| 209 | /// Writes a Single value
|
| 210 | /// </summary>
|
| 211 | protected override void Write(string field, float value)
|
| 212 | {
|
| 213 | if (float.IsNaN(value) || float.IsNegativeInfinity(value) || float.IsPositiveInfinity(value))
|
| 214 | throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity");
|
| 215 | base.Write(field, value);
|
| 216 | }
|
| 217 |
|
| 218 | // Treat enum as string
|
| 219 | protected override void WriteEnum(string field, int number, string name)
|
| 220 | {
|
| 221 | Write(field, name);
|
| 222 | }
|
| 223 |
|
| 224 | /// <summary>
|
| 225 | /// Writes an array of field values
|
| 226 | /// </summary>
|
| 227 | protected override void WriteArray(FieldType type, string field, System.Collections.IEnumerable items)
|
| 228 | {
|
| 229 | System.Collections.IEnumerator enumerator = items.GetEnumerator();
|
| 230 | try { if (!enumerator.MoveNext()) return; }
|
| 231 | finally { if (enumerator is IDisposable) ((IDisposable)enumerator).Dispose(); }
|
| 232 |
|
| 233 | WriteName(field);
|
| 234 | WriteToOutput("[");
|
| 235 | _counter.Add(0);
|
| 236 |
|
| 237 | base.WriteArray(type, String.Empty, items);
|
| 238 |
|
| 239 | _counter.RemoveAt(_counter.Count - 1);
|
| 240 | WriteLine("]");
|
| 241 | }
|
| 242 |
|
| 243 | /// <summary>
|
| 244 | /// Writes a message
|
| 245 | /// </summary>
|
| 246 | protected override void WriteMessageOrGroup(string field, IMessageLite message)
|
| 247 | {
|
| 248 | WriteName(field);
|
| 249 | WriteMessage(message);
|
| 250 | }
|
| 251 |
|
| 252 | /// <summary>
|
| 253 | /// Writes the message to the the formatted stream.
|
| 254 | /// </summary>
|
| 255 | public override void WriteMessage(IMessageLite message)
|
| 256 | {
|
| 257 | if (_isArray) Seperator();
|
| 258 | WriteToOutput("{");
|
| 259 | _counter.Add(0);
|
| 260 | message.WriteTo(this);
|
| 261 | _counter.RemoveAt(_counter.Count - 1);
|
| 262 | WriteLine("}");
|
| 263 | Flush();
|
| 264 | }
|
| 265 |
|
| 266 | /// <summary>
|
csharptest | afe844b | 2011-06-10 16:03:22 -0500 | [diff] [blame] | 267 | /// Used in streaming arrays of objects to the writer
|
| 268 | /// </summary>
|
| 269 | /// <example>
|
| 270 | /// <code>
|
| 271 | /// using(writer.StartArray())
|
| 272 | /// foreach(IMessageLite m in messages)
|
| 273 | /// writer.WriteMessage(m);
|
| 274 | /// </code>
|
| 275 | /// </example>
|
| 276 | public sealed class JsonArray : IDisposable
|
| 277 | {
|
| 278 | JsonFormatWriter _writer;
|
| 279 | internal JsonArray(JsonFormatWriter writer)
|
| 280 | {
|
| 281 | _writer = writer;
|
| 282 | _writer.WriteToOutput("[");
|
| 283 | _writer._counter.Add(0);
|
| 284 | }
|
| 285 |
|
| 286 | /// <summary>
|
| 287 | /// Causes the end of the array character to be written.
|
| 288 | /// </summary>
|
| 289 | void EndArray()
|
| 290 | {
|
| 291 | if (_writer != null)
|
| 292 | {
|
| 293 | _writer._counter.RemoveAt(_writer._counter.Count - 1);
|
| 294 | _writer.WriteLine("]");
|
| 295 | _writer.Flush();
|
| 296 | }
|
| 297 | _writer = null;
|
| 298 | }
|
| 299 | void IDisposable.Dispose() { EndArray(); }
|
| 300 | }
|
| 301 |
|
| 302 | /// <summary>
|
| 303 | /// Used to write an array of messages as the output rather than a single message.
|
| 304 | /// </summary>
|
| 305 | /// <example>
|
| 306 | /// <code>
|
| 307 | /// using(writer.StartArray())
|
| 308 | /// foreach(IMessageLite m in messages)
|
| 309 | /// writer.WriteMessage(m);
|
| 310 | /// </code>
|
| 311 | /// </example>
|
| 312 | public JsonArray StartArray()
|
| 313 | {
|
| 314 | if (_isArray) Seperator();
|
| 315 | _isArray = true;
|
| 316 | return new JsonArray(this);
|
| 317 | }
|
| 318 | }
|
| 319 | } |