Mercurial > pub > ImplabNet
annotate Implab/JSON/JSONWriter.cs @ 150:3258399cba83 v2
JSONWriter improvements
author | cin |
---|---|
date | Sat, 12 Dec 2015 22:12:44 +0300 |
parents | 2100965eb97f |
children |
rev | line source |
---|---|
55 | 1 using System; |
2 using System.Collections.Generic; | |
3 using System.IO; | |
142
2100965eb97f
fixed JSONWriter handling Infinity, NaN and locale aware number formatting
cin
parents:
141
diff
changeset
|
4 using System.Globalization; |
150 | 5 using System.Diagnostics; |
55 | 6 |
7 namespace Implab.JSON { | |
8 public class JSONWriter { | |
9 struct Context { | |
10 public bool needComma; | |
11 public JSONElementContext element; | |
12 } | |
13 Stack<Context> m_contextStack = new Stack<Context>(); | |
14 Context m_context; | |
15 | |
150 | 16 const int BUFFER_SIZE = 64; |
17 | |
55 | 18 TextWriter m_writer; |
72 | 19 readonly bool m_indent = true; |
20 readonly int m_indentSize = 4; | |
150 | 21 readonly char[] m_buffer = new char[BUFFER_SIZE]; |
22 int m_bufferPos; | |
55 | 23 |
150 | 24 static readonly char [] _hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; |
55 | 25 static readonly char [] _escapeBKS, |
26 _escapeFWD, | |
27 _escapeCR, | |
28 _escapeNL, | |
29 _escapeTAB, | |
30 _escapeBSLASH, | |
31 _escapeQ; | |
32 | |
33 static JSONWriter() { | |
34 _escapeBKS = "\\b".ToCharArray(); | |
35 _escapeFWD = "\\f".ToCharArray(); | |
36 _escapeCR = "\\r".ToCharArray(); | |
37 _escapeNL = "\\n".ToCharArray(); | |
38 _escapeTAB = "\\t".ToCharArray(); | |
39 _escapeBSLASH = "\\\\".ToCharArray(); | |
40 _escapeQ = "\\\"".ToCharArray(); | |
41 } | |
42 | |
43 public JSONWriter(TextWriter writer) { | |
44 Safe.ArgumentNotNull(writer, "writer"); | |
45 m_writer = writer; | |
46 } | |
47 | |
72 | 48 public JSONWriter(TextWriter writer, bool indent) { |
49 Safe.ArgumentNotNull(writer, "writer"); | |
50 | |
51 m_writer = writer; | |
52 m_indent = indent; | |
53 } | |
54 | |
55 void WriteIndent() { | |
56 if (m_indent) { | |
57 var indent = new char[m_contextStack.Count * m_indentSize + 1]; | |
58 indent[0] = '\n'; | |
59 for (int i = 1; i < indent.Length; i++) | |
60 indent[i] = ' '; | |
61 m_writer.Write(new String(indent)); | |
62 } else { | |
63 m_writer.Write(' '); | |
64 } | |
65 } | |
66 | |
55 | 67 void WriteMemberName(string name) { |
68 Safe.ArgumentNotEmpty(name, "name"); | |
69 if (m_context.element != JSONElementContext.Object) | |
70 OperationNotApplicable("WriteMember"); | |
71 if (m_context.needComma) | |
72 | 72 m_writer.Write(","); |
73 | |
74 WriteIndent(); | |
55 | 75 m_context.needComma = true; |
76 Write(name); | |
77 m_writer.Write(" : "); | |
78 } | |
79 | |
80 public void WriteValue(string name, string value) { | |
81 WriteMemberName(name); | |
82 Write(value); | |
83 } | |
84 | |
85 public void WriteValue(string name, bool value) { | |
86 WriteMemberName(name); | |
87 Write(value); | |
88 } | |
89 | |
90 public void WriteValue(string name, double value) { | |
91 WriteMemberName(name); | |
92 Write(value); | |
93 } | |
94 | |
95 public void WriteValue(string value) { | |
141 | 96 if (m_context.element == JSONElementContext.Array) { |
97 | |
98 if (m_context.needComma) | |
99 m_writer.Write(","); | |
100 WriteIndent(); | |
101 m_context.needComma = true; | |
102 | |
103 Write(value); | |
104 } else if (m_context.element == JSONElementContext.None) { | |
105 Write(value); | |
106 m_context.element = JSONElementContext.Closed; | |
107 } else { | |
55 | 108 OperationNotApplicable("WriteValue"); |
141 | 109 } |
55 | 110 } |
111 | |
112 public void WriteValue(bool value) { | |
141 | 113 if (m_context.element == JSONElementContext.Array) { |
114 | |
115 if (m_context.needComma) | |
116 m_writer.Write(","); | |
117 WriteIndent(); | |
118 m_context.needComma = true; | |
119 | |
120 Write(value); | |
121 } else if (m_context.element == JSONElementContext.None) { | |
122 Write(value); | |
123 m_context.element = JSONElementContext.Closed; | |
124 } else { | |
55 | 125 OperationNotApplicable("WriteValue"); |
141 | 126 } |
55 | 127 } |
128 | |
129 public void WriteValue(double value) { | |
141 | 130 if (m_context.element == JSONElementContext.Array) { |
131 | |
132 if (m_context.needComma) | |
133 m_writer.Write(","); | |
134 WriteIndent(); | |
135 m_context.needComma = true; | |
136 | |
137 Write(value); | |
138 } else if (m_context.element == JSONElementContext.None) { | |
139 Write(value); | |
140 m_context.element = JSONElementContext.Closed; | |
141 } else { | |
55 | 142 OperationNotApplicable("WriteValue"); |
141 | 143 } |
55 | 144 } |
145 | |
146 public void BeginObject() { | |
147 if (m_context.element != JSONElementContext.None && m_context.element != JSONElementContext.Array) | |
148 OperationNotApplicable("BeginObject"); | |
149 if (m_context.needComma) | |
72 | 150 m_writer.Write(","); |
151 | |
152 WriteIndent(); | |
153 | |
55 | 154 m_context.needComma = true; |
155 | |
156 m_contextStack.Push(m_context); | |
157 | |
158 m_context = new Context { element = JSONElementContext.Object, needComma = false }; | |
72 | 159 m_writer.Write("{"); |
55 | 160 } |
161 | |
162 public void BeginObject(string name) { | |
163 WriteMemberName(name); | |
164 | |
165 m_contextStack.Push(m_context); | |
166 | |
167 m_context = new Context { element = JSONElementContext.Object, needComma = false }; | |
72 | 168 m_writer.Write("{"); |
55 | 169 } |
170 | |
171 public void EndObject() { | |
172 if (m_context.element != JSONElementContext.Object) | |
141 | 173 OperationNotApplicable("EndObject"); |
72 | 174 |
55 | 175 m_context = m_contextStack.Pop(); |
141 | 176 if (m_contextStack.Count == 0) |
177 m_context.element = JSONElementContext.Closed; | |
72 | 178 WriteIndent(); |
179 m_writer.Write("}"); | |
55 | 180 } |
181 | |
182 public void BeginArray() { | |
183 if (m_context.element != JSONElementContext.None && m_context.element != JSONElementContext.Array) | |
184 throw new InvalidOperationException(); | |
72 | 185 if (m_context.needComma) { |
186 m_writer.Write(","); | |
187 | |
188 } | |
55 | 189 m_context.needComma = true; |
190 | |
72 | 191 WriteIndent(); |
55 | 192 m_contextStack.Push(m_context); |
193 m_context = new Context { element = JSONElementContext.Array, needComma = false }; | |
72 | 194 m_writer.Write("["); |
55 | 195 } |
196 | |
197 public void BeginArray(string name) { | |
198 WriteMemberName(name); | |
199 | |
200 m_contextStack.Push(m_context); | |
201 | |
202 m_context = new Context { element = JSONElementContext.Array, needComma = false }; | |
72 | 203 m_writer.Write("["); |
55 | 204 } |
205 | |
206 public void EndArray() { | |
207 if (m_context.element != JSONElementContext.Array) | |
208 OperationNotApplicable("EndArray"); | |
209 | |
210 m_context = m_contextStack.Pop(); | |
141 | 211 if (m_contextStack.Count == 0) |
212 m_context.element = JSONElementContext.Closed; | |
72 | 213 WriteIndent(); |
214 m_writer.Write("]"); | |
55 | 215 } |
216 | |
217 void Write(bool value) { | |
218 m_writer.Write(value ? "true" : "false"); | |
219 } | |
150 | 220 |
221 void FlushBuffer() { | |
222 if (m_bufferPos > 0) { | |
223 m_writer.Write(m_buffer, 0, m_bufferPos); | |
224 m_bufferPos = 0; | |
225 } | |
226 } | |
55 | 227 |
228 void Write(string value) { | |
115 | 229 if (value == null) { |
55 | 230 m_writer.Write("null"); |
115 | 231 return; |
232 } | |
55 | 233 |
150 | 234 Debug.Assert(m_bufferPos == 0); |
235 | |
55 | 236 var chars = value.ToCharArray(); |
150 | 237 m_buffer[m_bufferPos++] = '"'; |
238 | |
119
2573b562e328
Promises rewritten, added improved version of AsyncQueue
cin
parents:
118
diff
changeset
|
239 // Analysis disable once ForCanBeConvertedToForeach |
55 | 240 for (int i = 0; i < chars.Length; i++) { |
241 var ch = chars[i]; | |
242 | |
150 | 243 char[] escapeSeq; |
244 | |
55 | 245 switch (ch) { |
246 case '\b': | |
150 | 247 escapeSeq = _escapeBKS; |
55 | 248 break; |
249 case '\f': | |
150 | 250 escapeSeq = _escapeFWD; |
55 | 251 break; |
252 case '\r': | |
150 | 253 escapeSeq = _escapeCR; |
55 | 254 break; |
255 case '\n': | |
150 | 256 escapeSeq = _escapeNL; |
55 | 257 break; |
258 case '\t': | |
150 | 259 escapeSeq = _escapeTAB; |
55 | 260 break; |
261 case '\\': | |
150 | 262 escapeSeq = _escapeBSLASH; |
55 | 263 break; |
264 case '"': | |
150 | 265 escapeSeq = _escapeQ; |
55 | 266 break; |
267 default: | |
268 if (ch < 0x20) { | |
150 | 269 if (m_bufferPos + 6 > BUFFER_SIZE) |
270 FlushBuffer(); | |
271 | |
272 m_buffer[m_bufferPos++] = '\\'; | |
273 m_buffer[m_bufferPos++] = 'u'; | |
274 m_buffer[m_bufferPos++] = '0'; | |
275 m_buffer[m_bufferPos++] = '0'; | |
276 m_buffer[m_bufferPos++] = _hex[ch >> 4 & 0xf]; | |
277 m_buffer[m_bufferPos++] = _hex[ch & 0xf]; | |
278 | |
55 | 279 } else { |
150 | 280 if (m_bufferPos >= BUFFER_SIZE) |
281 FlushBuffer(); | |
282 m_buffer[m_bufferPos++] = ch; | |
55 | 283 } |
150 | 284 continue; |
55 | 285 } |
150 | 286 |
287 if (m_bufferPos + escapeSeq.Length > BUFFER_SIZE) | |
288 FlushBuffer(); | |
289 | |
290 Array.Copy(escapeSeq, 0, m_buffer, m_bufferPos, escapeSeq.Length); | |
291 m_bufferPos += escapeSeq.Length; | |
292 | |
55 | 293 } |
294 | |
150 | 295 if (m_bufferPos >= BUFFER_SIZE) |
296 FlushBuffer(); | |
297 | |
298 m_buffer[m_bufferPos++] = '"'; | |
299 | |
300 FlushBuffer(); | |
55 | 301 } |
302 | |
303 void Write(double value) { | |
142
2100965eb97f
fixed JSONWriter handling Infinity, NaN and locale aware number formatting
cin
parents:
141
diff
changeset
|
304 if (double.IsNaN(value)) |
2100965eb97f
fixed JSONWriter handling Infinity, NaN and locale aware number formatting
cin
parents:
141
diff
changeset
|
305 Write("NaN"); |
2100965eb97f
fixed JSONWriter handling Infinity, NaN and locale aware number formatting
cin
parents:
141
diff
changeset
|
306 else if (double.IsNegativeInfinity(value)) |
2100965eb97f
fixed JSONWriter handling Infinity, NaN and locale aware number formatting
cin
parents:
141
diff
changeset
|
307 Write("-Infinity"); |
2100965eb97f
fixed JSONWriter handling Infinity, NaN and locale aware number formatting
cin
parents:
141
diff
changeset
|
308 else if (double.IsPositiveInfinity(value)) |
2100965eb97f
fixed JSONWriter handling Infinity, NaN and locale aware number formatting
cin
parents:
141
diff
changeset
|
309 Write("Infinity"); |
2100965eb97f
fixed JSONWriter handling Infinity, NaN and locale aware number formatting
cin
parents:
141
diff
changeset
|
310 else |
2100965eb97f
fixed JSONWriter handling Infinity, NaN and locale aware number formatting
cin
parents:
141
diff
changeset
|
311 m_writer.Write(value.ToString(CultureInfo.InvariantCulture)); |
55 | 312 } |
313 | |
314 void OperationNotApplicable(string opName) { | |
315 throw new InvalidOperationException(String.Format("The operation '{0}' isn't applicable in the context of '{1}'", opName, m_context.element )); | |
316 } | |
317 | |
318 } | |
319 } |