|
@@
-1,251
+1,273
|
|
1
|
using System;
|
|
1
|
using System;
|
|
2
|
using System.Collections.Generic;
|
|
2
|
using System.Collections.Generic;
|
|
3
|
using System.IO;
|
|
3
|
using System.IO;
|
|
4
|
|
|
4
|
|
|
5
|
namespace Implab.JSON {
|
|
5
|
namespace Implab.JSON {
|
|
6
|
public class JSONWriter {
|
|
6
|
public class JSONWriter {
|
|
7
|
struct Context {
|
|
7
|
struct Context {
|
|
8
|
public bool needComma;
|
|
8
|
public bool needComma;
|
|
9
|
public JSONElementContext element;
|
|
9
|
public JSONElementContext element;
|
|
10
|
}
|
|
10
|
}
|
|
11
|
Stack<Context> m_contextStack = new Stack<Context>();
|
|
11
|
Stack<Context> m_contextStack = new Stack<Context>();
|
|
12
|
Context m_context;
|
|
12
|
Context m_context;
|
|
13
|
|
|
13
|
|
|
14
|
TextWriter m_writer;
|
|
14
|
TextWriter m_writer;
|
|
15
|
readonly bool m_indent = true;
|
|
15
|
readonly bool m_indent = true;
|
|
16
|
readonly int m_indentSize = 4;
|
|
16
|
readonly int m_indentSize = 4;
|
|
17
|
|
|
17
|
|
|
18
|
static readonly char [] _escapeBKS,
|
|
18
|
static readonly char [] _escapeBKS,
|
|
19
|
_escapeFWD,
|
|
19
|
_escapeFWD,
|
|
20
|
_escapeCR,
|
|
20
|
_escapeCR,
|
|
21
|
_escapeNL,
|
|
21
|
_escapeNL,
|
|
22
|
_escapeTAB,
|
|
22
|
_escapeTAB,
|
|
23
|
_escapeBSLASH,
|
|
23
|
_escapeBSLASH,
|
|
24
|
_escapeQ;
|
|
24
|
_escapeQ;
|
|
25
|
|
|
25
|
|
|
26
|
static JSONWriter() {
|
|
26
|
static JSONWriter() {
|
|
27
|
_escapeBKS = "\\b".ToCharArray();
|
|
27
|
_escapeBKS = "\\b".ToCharArray();
|
|
28
|
_escapeFWD = "\\f".ToCharArray();
|
|
28
|
_escapeFWD = "\\f".ToCharArray();
|
|
29
|
_escapeCR = "\\r".ToCharArray();
|
|
29
|
_escapeCR = "\\r".ToCharArray();
|
|
30
|
_escapeNL = "\\n".ToCharArray();
|
|
30
|
_escapeNL = "\\n".ToCharArray();
|
|
31
|
_escapeTAB = "\\t".ToCharArray();
|
|
31
|
_escapeTAB = "\\t".ToCharArray();
|
|
32
|
_escapeBSLASH = "\\\\".ToCharArray();
|
|
32
|
_escapeBSLASH = "\\\\".ToCharArray();
|
|
33
|
_escapeQ = "\\\"".ToCharArray();
|
|
33
|
_escapeQ = "\\\"".ToCharArray();
|
|
34
|
}
|
|
34
|
}
|
|
35
|
|
|
35
|
|
|
36
|
public JSONWriter(TextWriter writer) {
|
|
36
|
public JSONWriter(TextWriter writer) {
|
|
37
|
Safe.ArgumentNotNull(writer, "writer");
|
|
37
|
Safe.ArgumentNotNull(writer, "writer");
|
|
38
|
|
|
38
|
|
|
39
|
m_writer = writer;
|
|
39
|
m_writer = writer;
|
|
40
|
}
|
|
40
|
}
|
|
41
|
|
|
41
|
|
|
42
|
public JSONWriter(TextWriter writer, bool indent) {
|
|
42
|
public JSONWriter(TextWriter writer, bool indent) {
|
|
43
|
Safe.ArgumentNotNull(writer, "writer");
|
|
43
|
Safe.ArgumentNotNull(writer, "writer");
|
|
44
|
|
|
44
|
|
|
45
|
m_writer = writer;
|
|
45
|
m_writer = writer;
|
|
46
|
m_indent = indent;
|
|
46
|
m_indent = indent;
|
|
47
|
}
|
|
47
|
}
|
|
48
|
|
|
48
|
|
|
49
|
void WriteIndent() {
|
|
49
|
void WriteIndent() {
|
|
50
|
if (m_indent) {
|
|
50
|
if (m_indent) {
|
|
51
|
var indent = new char[m_contextStack.Count * m_indentSize + 1];
|
|
51
|
var indent = new char[m_contextStack.Count * m_indentSize + 1];
|
|
52
|
indent[0] = '\n';
|
|
52
|
indent[0] = '\n';
|
|
53
|
for (int i = 1; i < indent.Length; i++)
|
|
53
|
for (int i = 1; i < indent.Length; i++)
|
|
54
|
indent[i] = ' ';
|
|
54
|
indent[i] = ' ';
|
|
55
|
m_writer.Write(new String(indent));
|
|
55
|
m_writer.Write(new String(indent));
|
|
56
|
} else {
|
|
56
|
} else {
|
|
57
|
m_writer.Write(' ');
|
|
57
|
m_writer.Write(' ');
|
|
58
|
}
|
|
58
|
}
|
|
59
|
}
|
|
59
|
}
|
|
60
|
|
|
60
|
|
|
61
|
void WriteMemberName(string name) {
|
|
61
|
void WriteMemberName(string name) {
|
|
62
|
Safe.ArgumentNotEmpty(name, "name");
|
|
62
|
Safe.ArgumentNotEmpty(name, "name");
|
|
63
|
if (m_context.element != JSONElementContext.Object)
|
|
63
|
if (m_context.element != JSONElementContext.Object)
|
|
64
|
OperationNotApplicable("WriteMember");
|
|
64
|
OperationNotApplicable("WriteMember");
|
|
65
|
if (m_context.needComma)
|
|
65
|
if (m_context.needComma)
|
|
66
|
m_writer.Write(",");
|
|
66
|
m_writer.Write(",");
|
|
67
|
|
|
67
|
|
|
68
|
WriteIndent();
|
|
68
|
WriteIndent();
|
|
69
|
m_context.needComma = true;
|
|
69
|
m_context.needComma = true;
|
|
70
|
Write(name);
|
|
70
|
Write(name);
|
|
71
|
m_writer.Write(" : ");
|
|
71
|
m_writer.Write(" : ");
|
|
72
|
}
|
|
72
|
}
|
|
73
|
|
|
73
|
|
|
74
|
public void WriteValue(string name, string value) {
|
|
74
|
public void WriteValue(string name, string value) {
|
|
75
|
WriteMemberName(name);
|
|
75
|
WriteMemberName(name);
|
|
76
|
Write(value);
|
|
76
|
Write(value);
|
|
77
|
}
|
|
77
|
}
|
|
78
|
|
|
78
|
|
|
79
|
public void WriteValue(string name, bool value) {
|
|
79
|
public void WriteValue(string name, bool value) {
|
|
80
|
WriteMemberName(name);
|
|
80
|
WriteMemberName(name);
|
|
81
|
Write(value);
|
|
81
|
Write(value);
|
|
82
|
}
|
|
82
|
}
|
|
83
|
|
|
83
|
|
|
84
|
public void WriteValue(string name, double value) {
|
|
84
|
public void WriteValue(string name, double value) {
|
|
85
|
WriteMemberName(name);
|
|
85
|
WriteMemberName(name);
|
|
86
|
Write(value);
|
|
86
|
Write(value);
|
|
87
|
}
|
|
87
|
}
|
|
88
|
|
|
88
|
|
|
89
|
public void WriteValue(string value) {
|
|
89
|
public void WriteValue(string value) {
|
|
90
|
if (m_context.element != JSONElementContext.Array)
|
|
90
|
if (m_context.element == JSONElementContext.Array) {
|
|
91
|
OperationNotApplicable("WriteValue");
|
|
91
|
|
|
92
|
if (m_context.needComma)
|
|
92
|
if (m_context.needComma)
|
|
93
|
m_writer.Write(",");
|
|
93
|
m_writer.Write(",");
|
|
94
|
WriteIndent();
|
|
94
|
WriteIndent();
|
|
95
|
m_context.needComma = true;
|
|
95
|
m_context.needComma = true;
|
|
96
|
|
|
96
|
|
|
97
|
Write(value);
|
|
97
|
Write(value);
|
|
|
|
|
98
|
} else if (m_context.element == JSONElementContext.None) {
|
|
|
|
|
99
|
Write(value);
|
|
|
|
|
100
|
m_context.element = JSONElementContext.Closed;
|
|
|
|
|
101
|
} else {
|
|
|
|
|
102
|
OperationNotApplicable("WriteValue");
|
|
|
|
|
103
|
}
|
|
98
|
}
|
|
104
|
}
|
|
99
|
|
|
105
|
|
|
100
|
public void WriteValue(bool value) {
|
|
106
|
public void WriteValue(bool value) {
|
|
101
|
if (m_context.element != JSONElementContext.Array)
|
|
107
|
if (m_context.element == JSONElementContext.Array) {
|
|
102
|
OperationNotApplicable("WriteValue");
|
|
108
|
|
|
103
|
if (m_context.needComma)
|
|
109
|
if (m_context.needComma)
|
|
104
|
m_writer.Write(",");
|
|
110
|
m_writer.Write(",");
|
|
|
|
|
111
|
WriteIndent();
|
|
105
|
m_context.needComma = true;
|
|
112
|
m_context.needComma = true;
|
|
106
|
|
|
113
|
|
|
107
|
WriteIndent();
|
|
114
|
Write(value);
|
|
|
|
|
115
|
} else if (m_context.element == JSONElementContext.None) {
|
|
108
|
Write(value);
|
|
116
|
Write(value);
|
|
|
|
|
117
|
m_context.element = JSONElementContext.Closed;
|
|
|
|
|
118
|
} else {
|
|
|
|
|
119
|
OperationNotApplicable("WriteValue");
|
|
|
|
|
120
|
}
|
|
109
|
}
|
|
121
|
}
|
|
110
|
|
|
122
|
|
|
111
|
public void WriteValue(double value) {
|
|
123
|
public void WriteValue(double value) {
|
|
112
|
if (m_context.element != JSONElementContext.Array)
|
|
124
|
if (m_context.element == JSONElementContext.Array) {
|
|
113
|
OperationNotApplicable("WriteValue");
|
|
125
|
|
|
114
|
if (m_context.needComma)
|
|
126
|
if (m_context.needComma)
|
|
115
|
m_writer.Write(",");
|
|
127
|
m_writer.Write(",");
|
|
|
|
|
128
|
WriteIndent();
|
|
116
|
m_context.needComma = true;
|
|
129
|
m_context.needComma = true;
|
|
117
|
|
|
130
|
|
|
118
|
WriteIndent();
|
|
131
|
Write(value);
|
|
|
|
|
132
|
} else if (m_context.element == JSONElementContext.None) {
|
|
119
|
Write(value);
|
|
133
|
Write(value);
|
|
|
|
|
134
|
m_context.element = JSONElementContext.Closed;
|
|
|
|
|
135
|
} else {
|
|
|
|
|
136
|
OperationNotApplicable("WriteValue");
|
|
|
|
|
137
|
}
|
|
120
|
}
|
|
138
|
}
|
|
121
|
|
|
139
|
|
|
122
|
public void BeginObject() {
|
|
140
|
public void BeginObject() {
|
|
123
|
if (m_context.element != JSONElementContext.None && m_context.element != JSONElementContext.Array)
|
|
141
|
if (m_context.element != JSONElementContext.None && m_context.element != JSONElementContext.Array)
|
|
124
|
OperationNotApplicable("BeginObject");
|
|
142
|
OperationNotApplicable("BeginObject");
|
|
125
|
if (m_context.needComma)
|
|
143
|
if (m_context.needComma)
|
|
126
|
m_writer.Write(",");
|
|
144
|
m_writer.Write(",");
|
|
127
|
|
|
145
|
|
|
128
|
WriteIndent();
|
|
146
|
WriteIndent();
|
|
129
|
|
|
147
|
|
|
130
|
m_context.needComma = true;
|
|
148
|
m_context.needComma = true;
|
|
131
|
|
|
149
|
|
|
132
|
m_contextStack.Push(m_context);
|
|
150
|
m_contextStack.Push(m_context);
|
|
133
|
|
|
151
|
|
|
134
|
m_context = new Context { element = JSONElementContext.Object, needComma = false };
|
|
152
|
m_context = new Context { element = JSONElementContext.Object, needComma = false };
|
|
135
|
m_writer.Write("{");
|
|
153
|
m_writer.Write("{");
|
|
136
|
}
|
|
154
|
}
|
|
137
|
|
|
155
|
|
|
138
|
public void BeginObject(string name) {
|
|
156
|
public void BeginObject(string name) {
|
|
139
|
WriteMemberName(name);
|
|
157
|
WriteMemberName(name);
|
|
140
|
|
|
158
|
|
|
141
|
m_contextStack.Push(m_context);
|
|
159
|
m_contextStack.Push(m_context);
|
|
142
|
|
|
160
|
|
|
143
|
m_context = new Context { element = JSONElementContext.Object, needComma = false };
|
|
161
|
m_context = new Context { element = JSONElementContext.Object, needComma = false };
|
|
144
|
m_writer.Write("{");
|
|
162
|
m_writer.Write("{");
|
|
145
|
}
|
|
163
|
}
|
|
146
|
|
|
164
|
|
|
147
|
public void EndObject() {
|
|
165
|
public void EndObject() {
|
|
148
|
if (m_context.element != JSONElementContext.Object)
|
|
166
|
if (m_context.element != JSONElementContext.Object)
|
|
149
|
OperationNotApplicable("EndArray");
|
|
167
|
OperationNotApplicable("EndObject");
|
|
150
|
|
|
168
|
|
|
151
|
m_context = m_contextStack.Pop();
|
|
169
|
m_context = m_contextStack.Pop();
|
|
|
|
|
170
|
if (m_contextStack.Count == 0)
|
|
|
|
|
171
|
m_context.element = JSONElementContext.Closed;
|
|
152
|
WriteIndent();
|
|
172
|
WriteIndent();
|
|
153
|
m_writer.Write("}");
|
|
173
|
m_writer.Write("}");
|
|
154
|
}
|
|
174
|
}
|
|
155
|
|
|
175
|
|
|
156
|
public void BeginArray() {
|
|
176
|
public void BeginArray() {
|
|
157
|
if (m_context.element != JSONElementContext.None && m_context.element != JSONElementContext.Array)
|
|
177
|
if (m_context.element != JSONElementContext.None && m_context.element != JSONElementContext.Array)
|
|
158
|
throw new InvalidOperationException();
|
|
178
|
throw new InvalidOperationException();
|
|
159
|
if (m_context.needComma) {
|
|
179
|
if (m_context.needComma) {
|
|
160
|
m_writer.Write(",");
|
|
180
|
m_writer.Write(",");
|
|
161
|
|
|
181
|
|
|
162
|
}
|
|
182
|
}
|
|
163
|
m_context.needComma = true;
|
|
183
|
m_context.needComma = true;
|
|
164
|
|
|
184
|
|
|
165
|
WriteIndent();
|
|
185
|
WriteIndent();
|
|
166
|
m_contextStack.Push(m_context);
|
|
186
|
m_contextStack.Push(m_context);
|
|
167
|
m_context = new Context { element = JSONElementContext.Array, needComma = false };
|
|
187
|
m_context = new Context { element = JSONElementContext.Array, needComma = false };
|
|
168
|
m_writer.Write("[");
|
|
188
|
m_writer.Write("[");
|
|
169
|
}
|
|
189
|
}
|
|
170
|
|
|
190
|
|
|
171
|
public void BeginArray(string name) {
|
|
191
|
public void BeginArray(string name) {
|
|
172
|
WriteMemberName(name);
|
|
192
|
WriteMemberName(name);
|
|
173
|
|
|
193
|
|
|
174
|
m_contextStack.Push(m_context);
|
|
194
|
m_contextStack.Push(m_context);
|
|
175
|
|
|
195
|
|
|
176
|
m_context = new Context { element = JSONElementContext.Array, needComma = false };
|
|
196
|
m_context = new Context { element = JSONElementContext.Array, needComma = false };
|
|
177
|
m_writer.Write("[");
|
|
197
|
m_writer.Write("[");
|
|
178
|
}
|
|
198
|
}
|
|
179
|
|
|
199
|
|
|
180
|
public void EndArray() {
|
|
200
|
public void EndArray() {
|
|
181
|
if (m_context.element != JSONElementContext.Array)
|
|
201
|
if (m_context.element != JSONElementContext.Array)
|
|
182
|
OperationNotApplicable("EndArray");
|
|
202
|
OperationNotApplicable("EndArray");
|
|
183
|
|
|
203
|
|
|
184
|
m_context = m_contextStack.Pop();
|
|
204
|
m_context = m_contextStack.Pop();
|
|
|
|
|
205
|
if (m_contextStack.Count == 0)
|
|
|
|
|
206
|
m_context.element = JSONElementContext.Closed;
|
|
185
|
WriteIndent();
|
|
207
|
WriteIndent();
|
|
186
|
m_writer.Write("]");
|
|
208
|
m_writer.Write("]");
|
|
187
|
}
|
|
209
|
}
|
|
188
|
|
|
210
|
|
|
189
|
void Write(bool value) {
|
|
211
|
void Write(bool value) {
|
|
190
|
m_writer.Write(value ? "true" : "false");
|
|
212
|
m_writer.Write(value ? "true" : "false");
|
|
191
|
}
|
|
213
|
}
|
|
192
|
|
|
214
|
|
|
193
|
|
|
215
|
|
|
194
|
void Write(string value) {
|
|
216
|
void Write(string value) {
|
|
195
|
if (value == null) {
|
|
217
|
if (value == null) {
|
|
196
|
m_writer.Write("null");
|
|
218
|
m_writer.Write("null");
|
|
197
|
return;
|
|
219
|
return;
|
|
198
|
}
|
|
220
|
}
|
|
199
|
|
|
221
|
|
|
200
|
var chars = value.ToCharArray();
|
|
222
|
var chars = value.ToCharArray();
|
|
201
|
m_writer.Write('"');
|
|
223
|
m_writer.Write('"');
|
|
202
|
|
|
224
|
|
|
203
|
// Analysis disable once ForCanBeConvertedToForeach
|
|
225
|
// Analysis disable once ForCanBeConvertedToForeach
|
|
204
|
for (int i = 0; i < chars.Length; i++) {
|
|
226
|
for (int i = 0; i < chars.Length; i++) {
|
|
205
|
var ch = chars[i];
|
|
227
|
var ch = chars[i];
|
|
206
|
|
|
228
|
|
|
207
|
switch (ch) {
|
|
229
|
switch (ch) {
|
|
208
|
case '\b':
|
|
230
|
case '\b':
|
|
209
|
m_writer.Write(_escapeBKS);
|
|
231
|
m_writer.Write(_escapeBKS);
|
|
210
|
break;
|
|
232
|
break;
|
|
211
|
case '\f':
|
|
233
|
case '\f':
|
|
212
|
m_writer.Write(_escapeFWD);
|
|
234
|
m_writer.Write(_escapeFWD);
|
|
213
|
break;
|
|
235
|
break;
|
|
214
|
case '\r':
|
|
236
|
case '\r':
|
|
215
|
m_writer.Write(_escapeCR);
|
|
237
|
m_writer.Write(_escapeCR);
|
|
216
|
break;
|
|
238
|
break;
|
|
217
|
case '\n':
|
|
239
|
case '\n':
|
|
218
|
m_writer.Write(_escapeNL);
|
|
240
|
m_writer.Write(_escapeNL);
|
|
219
|
break;
|
|
241
|
break;
|
|
220
|
case '\t':
|
|
242
|
case '\t':
|
|
221
|
m_writer.Write(_escapeTAB);
|
|
243
|
m_writer.Write(_escapeTAB);
|
|
222
|
break;
|
|
244
|
break;
|
|
223
|
case '\\':
|
|
245
|
case '\\':
|
|
224
|
m_writer.Write(_escapeBSLASH);
|
|
246
|
m_writer.Write(_escapeBSLASH);
|
|
225
|
break;
|
|
247
|
break;
|
|
226
|
case '"':
|
|
248
|
case '"':
|
|
227
|
m_writer.Write(_escapeQ);
|
|
249
|
m_writer.Write(_escapeQ);
|
|
228
|
break;
|
|
250
|
break;
|
|
229
|
default:
|
|
251
|
default:
|
|
230
|
if (ch < 0x20) {
|
|
252
|
if (ch < 0x20) {
|
|
231
|
m_writer.Write("\\u00{0:x2}",(int)ch);
|
|
253
|
m_writer.Write("\\u00{0:x2}",(int)ch);
|
|
232
|
} else {
|
|
254
|
} else {
|
|
233
|
m_writer.Write(ch);
|
|
255
|
m_writer.Write(ch);
|
|
234
|
}
|
|
256
|
}
|
|
235
|
break;
|
|
257
|
break;
|
|
236
|
}
|
|
258
|
}
|
|
237
|
}
|
|
259
|
}
|
|
238
|
|
|
260
|
|
|
239
|
m_writer.Write('"');
|
|
261
|
m_writer.Write('"');
|
|
240
|
}
|
|
262
|
}
|
|
241
|
|
|
263
|
|
|
242
|
void Write(double value) {
|
|
264
|
void Write(double value) {
|
|
243
|
m_writer.Write(value);
|
|
265
|
m_writer.Write(value);
|
|
244
|
}
|
|
266
|
}
|
|
245
|
|
|
267
|
|
|
246
|
void OperationNotApplicable(string opName) {
|
|
268
|
void OperationNotApplicable(string opName) {
|
|
247
|
throw new InvalidOperationException(String.Format("The operation '{0}' isn't applicable in the context of '{1}'", opName, m_context.element ));
|
|
269
|
throw new InvalidOperationException(String.Format("The operation '{0}' isn't applicable in the context of '{1}'", opName, m_context.element ));
|
|
248
|
}
|
|
270
|
}
|
|
249
|
|
|
271
|
|
|
250
|
}
|
|
272
|
}
|
|
251
|
}
|
|
273
|
}
|