blob: 5d5144ba02a0cc006ed3ff5686447bd5d3f11dca [file] [log] [blame]
csharptestafe844b2011-06-10 16:03:22 -05001using System;
2using System.Collections.Generic;
3using System.IO;
4using Google.ProtocolBuffers.Descriptors;
5
6namespace 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>
267 /// Writes a message
268 /// </summary>
269 [System.ComponentModel.Browsable(false)]
270 [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
271 public override void WriteMessage(string field, IMessageLite message)
272 {
273 WriteMessage(message);
274 }
275
276 /// <summary>
277 /// Used in streaming arrays of objects to the writer
278 /// </summary>
279 /// <example>
280 /// <code>
281 /// using(writer.StartArray())
282 /// foreach(IMessageLite m in messages)
283 /// writer.WriteMessage(m);
284 /// </code>
285 /// </example>
286 public sealed class JsonArray : IDisposable
287 {
288 JsonFormatWriter _writer;
289 internal JsonArray(JsonFormatWriter writer)
290 {
291 _writer = writer;
292 _writer.WriteToOutput("[");
293 _writer._counter.Add(0);
294 }
295
296 /// <summary>
297 /// Causes the end of the array character to be written.
298 /// </summary>
299 void EndArray()
300 {
301 if (_writer != null)
302 {
303 _writer._counter.RemoveAt(_writer._counter.Count - 1);
304 _writer.WriteLine("]");
305 _writer.Flush();
306 }
307 _writer = null;
308 }
309 void IDisposable.Dispose() { EndArray(); }
310 }
311
312 /// <summary>
313 /// Used to write an array of messages as the output rather than a single message.
314 /// </summary>
315 /// <example>
316 /// <code>
317 /// using(writer.StartArray())
318 /// foreach(IMessageLite m in messages)
319 /// writer.WriteMessage(m);
320 /// </code>
321 /// </example>
322 public JsonArray StartArray()
323 {
324 if (_isArray) Seperator();
325 _isArray = true;
326 return new JsonArray(this);
327 }
328 }
329}