145
|
1 // Written in the D programming language.
|
|
2
|
|
3 /**
|
|
4 JavaScript Object Notation
|
|
5
|
|
6 Copyright: Copyright Jeremie Pelletier 2008 - 2009.
|
|
7 License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
|
8 Authors: Jeremie Pelletier, David Herberth
|
|
9 References: $(LINK http://json.org/)
|
|
10 Source: $(PHOBOSSRC std/_json.d)
|
|
11 */
|
|
12 /*
|
|
13 Copyright Jeremie Pelletier 2008 - 2009.
|
|
14 Distributed under the Boost Software License, Version 1.0.
|
|
15 (See accompanying file LICENSE_1_0.txt or copy at
|
|
16 http://www.boost.org/LICENSE_1_0.txt)
|
|
17 */
|
|
18 module std.json;
|
|
19
|
|
20 import std.array;
|
|
21 import std.conv;
|
|
22 import std.range.primitives;
|
|
23 import std.traits;
|
|
24
|
|
25 ///
|
|
26 @system unittest
|
|
27 {
|
|
28 import std.conv : to;
|
|
29
|
|
30 // parse a file or string of json into a usable structure
|
|
31 string s = `{ "language": "D", "rating": 3.5, "code": "42" }`;
|
|
32 JSONValue j = parseJSON(s);
|
|
33 // j and j["language"] return JSONValue,
|
|
34 // j["language"].str returns a string
|
|
35 assert(j["language"].str == "D");
|
|
36 assert(j["rating"].floating == 3.5);
|
|
37
|
|
38 // check a type
|
|
39 long x;
|
|
40 if (const(JSONValue)* code = "code" in j)
|
|
41 {
|
|
42 if (code.type() == JSON_TYPE.INTEGER)
|
|
43 x = code.integer;
|
|
44 else
|
|
45 x = to!int(code.str);
|
|
46 }
|
|
47
|
|
48 // create a json struct
|
|
49 JSONValue jj = [ "language": "D" ];
|
|
50 // rating doesnt exist yet, so use .object to assign
|
|
51 jj.object["rating"] = JSONValue(3.5);
|
|
52 // create an array to assign to list
|
|
53 jj.object["list"] = JSONValue( ["a", "b", "c"] );
|
|
54 // list already exists, so .object optional
|
|
55 jj["list"].array ~= JSONValue("D");
|
|
56
|
|
57 string jjStr = `{"language":"D","list":["a","b","c","D"],"rating":3.5}`;
|
|
58 assert(jj.toString == jjStr);
|
|
59 }
|
|
60
|
|
61 /**
|
|
62 String literals used to represent special float values within JSON strings.
|
|
63 */
|
|
64 enum JSONFloatLiteral : string
|
|
65 {
|
|
66 nan = "NaN", /// string representation of floating-point NaN
|
|
67 inf = "Infinite", /// string representation of floating-point Infinity
|
|
68 negativeInf = "-Infinite", /// string representation of floating-point negative Infinity
|
|
69 }
|
|
70
|
|
71 /**
|
|
72 Flags that control how json is encoded and parsed.
|
|
73 */
|
|
74 enum JSONOptions
|
|
75 {
|
|
76 none, /// standard parsing
|
|
77 specialFloatLiterals = 0x1, /// encode NaN and Inf float values as strings
|
|
78 escapeNonAsciiChars = 0x2, /// encode non ascii characters with an unicode escape sequence
|
|
79 doNotEscapeSlashes = 0x4, /// do not escape slashes ('/')
|
|
80 }
|
|
81
|
|
82 /**
|
|
83 JSON type enumeration
|
|
84 */
|
|
85 enum JSON_TYPE : byte
|
|
86 {
|
|
87 /// Indicates the type of a $(D JSONValue).
|
|
88 NULL,
|
|
89 STRING, /// ditto
|
|
90 INTEGER, /// ditto
|
|
91 UINTEGER,/// ditto
|
|
92 FLOAT, /// ditto
|
|
93 OBJECT, /// ditto
|
|
94 ARRAY, /// ditto
|
|
95 TRUE, /// ditto
|
|
96 FALSE /// ditto
|
|
97 }
|
|
98
|
|
99 /**
|
|
100 JSON value node
|
|
101 */
|
|
102 struct JSONValue
|
|
103 {
|
|
104 import std.exception : enforceEx, enforce;
|
|
105
|
|
106 union Store
|
|
107 {
|
|
108 string str;
|
|
109 long integer;
|
|
110 ulong uinteger;
|
|
111 double floating;
|
|
112 JSONValue[string] object;
|
|
113 JSONValue[] array;
|
|
114 }
|
|
115 private Store store;
|
|
116 private JSON_TYPE type_tag;
|
|
117
|
|
118 /**
|
|
119 Returns the JSON_TYPE of the value stored in this structure.
|
|
120 */
|
|
121 @property JSON_TYPE type() const pure nothrow @safe @nogc
|
|
122 {
|
|
123 return type_tag;
|
|
124 }
|
|
125 ///
|
|
126 @safe unittest
|
|
127 {
|
|
128 string s = "{ \"language\": \"D\" }";
|
|
129 JSONValue j = parseJSON(s);
|
|
130 assert(j.type == JSON_TYPE.OBJECT);
|
|
131 assert(j["language"].type == JSON_TYPE.STRING);
|
|
132 }
|
|
133
|
|
134 /***
|
|
135 * Value getter/setter for $(D JSON_TYPE.STRING).
|
|
136 * Throws: $(D JSONException) for read access if $(D type) is not
|
|
137 * $(D JSON_TYPE.STRING).
|
|
138 */
|
|
139 @property string str() const pure @trusted
|
|
140 {
|
|
141 enforce!JSONException(type == JSON_TYPE.STRING,
|
|
142 "JSONValue is not a string");
|
|
143 return store.str;
|
|
144 }
|
|
145 /// ditto
|
|
146 @property string str(string v) pure nothrow @nogc @safe
|
|
147 {
|
|
148 assign(v);
|
|
149 return v;
|
|
150 }
|
|
151 ///
|
|
152 @safe unittest
|
|
153 {
|
|
154 JSONValue j = [ "language": "D" ];
|
|
155
|
|
156 // get value
|
|
157 assert(j["language"].str == "D");
|
|
158
|
|
159 // change existing key to new string
|
|
160 j["language"].str = "Perl";
|
|
161 assert(j["language"].str == "Perl");
|
|
162 }
|
|
163
|
|
164 /***
|
|
165 * Value getter/setter for $(D JSON_TYPE.INTEGER).
|
|
166 * Throws: $(D JSONException) for read access if $(D type) is not
|
|
167 * $(D JSON_TYPE.INTEGER).
|
|
168 */
|
|
169 @property inout(long) integer() inout pure @safe
|
|
170 {
|
|
171 enforce!JSONException(type == JSON_TYPE.INTEGER,
|
|
172 "JSONValue is not an integer");
|
|
173 return store.integer;
|
|
174 }
|
|
175 /// ditto
|
|
176 @property long integer(long v) pure nothrow @safe @nogc
|
|
177 {
|
|
178 assign(v);
|
|
179 return store.integer;
|
|
180 }
|
|
181
|
|
182 /***
|
|
183 * Value getter/setter for $(D JSON_TYPE.UINTEGER).
|
|
184 * Throws: $(D JSONException) for read access if $(D type) is not
|
|
185 * $(D JSON_TYPE.UINTEGER).
|
|
186 */
|
|
187 @property inout(ulong) uinteger() inout pure @safe
|
|
188 {
|
|
189 enforce!JSONException(type == JSON_TYPE.UINTEGER,
|
|
190 "JSONValue is not an unsigned integer");
|
|
191 return store.uinteger;
|
|
192 }
|
|
193 /// ditto
|
|
194 @property ulong uinteger(ulong v) pure nothrow @safe @nogc
|
|
195 {
|
|
196 assign(v);
|
|
197 return store.uinteger;
|
|
198 }
|
|
199
|
|
200 /***
|
|
201 * Value getter/setter for $(D JSON_TYPE.FLOAT). Note that despite
|
|
202 * the name, this is a $(B 64)-bit `double`, not a 32-bit `float`.
|
|
203 * Throws: $(D JSONException) for read access if $(D type) is not
|
|
204 * $(D JSON_TYPE.FLOAT).
|
|
205 */
|
|
206 @property inout(double) floating() inout pure @safe
|
|
207 {
|
|
208 enforce!JSONException(type == JSON_TYPE.FLOAT,
|
|
209 "JSONValue is not a floating type");
|
|
210 return store.floating;
|
|
211 }
|
|
212 /// ditto
|
|
213 @property double floating(double v) pure nothrow @safe @nogc
|
|
214 {
|
|
215 assign(v);
|
|
216 return store.floating;
|
|
217 }
|
|
218
|
|
219 /***
|
|
220 * Value getter/setter for $(D JSON_TYPE.OBJECT).
|
|
221 * Throws: $(D JSONException) for read access if $(D type) is not
|
|
222 * $(D JSON_TYPE.OBJECT).
|
|
223 * Note: this is @system because of the following pattern:
|
|
224 ---
|
|
225 auto a = &(json.object());
|
|
226 json.uinteger = 0; // overwrite AA pointer
|
|
227 (*a)["hello"] = "world"; // segmentation fault
|
|
228 ---
|
|
229 */
|
|
230 @property ref inout(JSONValue[string]) object() inout pure @system
|
|
231 {
|
|
232 enforce!JSONException(type == JSON_TYPE.OBJECT,
|
|
233 "JSONValue is not an object");
|
|
234 return store.object;
|
|
235 }
|
|
236 /// ditto
|
|
237 @property JSONValue[string] object(JSONValue[string] v) pure nothrow @nogc @safe
|
|
238 {
|
|
239 assign(v);
|
|
240 return v;
|
|
241 }
|
|
242
|
|
243 /***
|
|
244 * Value getter for $(D JSON_TYPE.OBJECT).
|
|
245 * Unlike $(D object), this retrieves the object by value and can be used in @safe code.
|
|
246 *
|
|
247 * A caveat is that, if the returned value is null, modifications will not be visible:
|
|
248 * ---
|
|
249 * JSONValue json;
|
|
250 * json.object = null;
|
|
251 * json.objectNoRef["hello"] = JSONValue("world");
|
|
252 * assert("hello" !in json.object);
|
|
253 * ---
|
|
254 *
|
|
255 * Throws: $(D JSONException) for read access if $(D type) is not
|
|
256 * $(D JSON_TYPE.OBJECT).
|
|
257 */
|
|
258 @property inout(JSONValue[string]) objectNoRef() inout pure @trusted
|
|
259 {
|
|
260 enforce!JSONException(type == JSON_TYPE.OBJECT,
|
|
261 "JSONValue is not an object");
|
|
262 return store.object;
|
|
263 }
|
|
264
|
|
265 /***
|
|
266 * Value getter/setter for $(D JSON_TYPE.ARRAY).
|
|
267 * Throws: $(D JSONException) for read access if $(D type) is not
|
|
268 * $(D JSON_TYPE.ARRAY).
|
|
269 * Note: this is @system because of the following pattern:
|
|
270 ---
|
|
271 auto a = &(json.array());
|
|
272 json.uinteger = 0; // overwrite array pointer
|
|
273 (*a)[0] = "world"; // segmentation fault
|
|
274 ---
|
|
275 */
|
|
276 @property ref inout(JSONValue[]) array() inout pure @system
|
|
277 {
|
|
278 enforce!JSONException(type == JSON_TYPE.ARRAY,
|
|
279 "JSONValue is not an array");
|
|
280 return store.array;
|
|
281 }
|
|
282 /// ditto
|
|
283 @property JSONValue[] array(JSONValue[] v) pure nothrow @nogc @safe
|
|
284 {
|
|
285 assign(v);
|
|
286 return v;
|
|
287 }
|
|
288
|
|
289 /***
|
|
290 * Value getter for $(D JSON_TYPE.ARRAY).
|
|
291 * Unlike $(D array), this retrieves the array by value and can be used in @safe code.
|
|
292 *
|
|
293 * A caveat is that, if you append to the returned array, the new values aren't visible in the
|
|
294 * JSONValue:
|
|
295 * ---
|
|
296 * JSONValue json;
|
|
297 * json.array = [JSONValue("hello")];
|
|
298 * json.arrayNoRef ~= JSONValue("world");
|
|
299 * assert(json.array.length == 1);
|
|
300 * ---
|
|
301 *
|
|
302 * Throws: $(D JSONException) for read access if $(D type) is not
|
|
303 * $(D JSON_TYPE.ARRAY).
|
|
304 */
|
|
305 @property inout(JSONValue[]) arrayNoRef() inout pure @trusted
|
|
306 {
|
|
307 enforce!JSONException(type == JSON_TYPE.ARRAY,
|
|
308 "JSONValue is not an array");
|
|
309 return store.array;
|
|
310 }
|
|
311
|
|
312 /// Test whether the type is $(D JSON_TYPE.NULL)
|
|
313 @property bool isNull() const pure nothrow @safe @nogc
|
|
314 {
|
|
315 return type == JSON_TYPE.NULL;
|
|
316 }
|
|
317
|
|
318 private void assign(T)(T arg) @safe
|
|
319 {
|
|
320 static if (is(T : typeof(null)))
|
|
321 {
|
|
322 type_tag = JSON_TYPE.NULL;
|
|
323 }
|
|
324 else static if (is(T : string))
|
|
325 {
|
|
326 type_tag = JSON_TYPE.STRING;
|
|
327 string t = arg;
|
|
328 () @trusted { store.str = t; }();
|
|
329 }
|
|
330 else static if (isSomeString!T) // issue 15884
|
|
331 {
|
|
332 type_tag = JSON_TYPE.STRING;
|
|
333 // FIXME: std.array.array(Range) is not deduced as 'pure'
|
|
334 () @trusted {
|
|
335 import std.utf : byUTF;
|
|
336 store.str = cast(immutable)(arg.byUTF!char.array);
|
|
337 }();
|
|
338 }
|
|
339 else static if (is(T : bool))
|
|
340 {
|
|
341 type_tag = arg ? JSON_TYPE.TRUE : JSON_TYPE.FALSE;
|
|
342 }
|
|
343 else static if (is(T : ulong) && isUnsigned!T)
|
|
344 {
|
|
345 type_tag = JSON_TYPE.UINTEGER;
|
|
346 store.uinteger = arg;
|
|
347 }
|
|
348 else static if (is(T : long))
|
|
349 {
|
|
350 type_tag = JSON_TYPE.INTEGER;
|
|
351 store.integer = arg;
|
|
352 }
|
|
353 else static if (isFloatingPoint!T)
|
|
354 {
|
|
355 type_tag = JSON_TYPE.FLOAT;
|
|
356 store.floating = arg;
|
|
357 }
|
|
358 else static if (is(T : Value[Key], Key, Value))
|
|
359 {
|
|
360 static assert(is(Key : string), "AA key must be string");
|
|
361 type_tag = JSON_TYPE.OBJECT;
|
|
362 static if (is(Value : JSONValue))
|
|
363 {
|
|
364 JSONValue[string] t = arg;
|
|
365 () @trusted { store.object = t; }();
|
|
366 }
|
|
367 else
|
|
368 {
|
|
369 JSONValue[string] aa;
|
|
370 foreach (key, value; arg)
|
|
371 aa[key] = JSONValue(value);
|
|
372 () @trusted { store.object = aa; }();
|
|
373 }
|
|
374 }
|
|
375 else static if (isArray!T)
|
|
376 {
|
|
377 type_tag = JSON_TYPE.ARRAY;
|
|
378 static if (is(ElementEncodingType!T : JSONValue))
|
|
379 {
|
|
380 JSONValue[] t = arg;
|
|
381 () @trusted { store.array = t; }();
|
|
382 }
|
|
383 else
|
|
384 {
|
|
385 JSONValue[] new_arg = new JSONValue[arg.length];
|
|
386 foreach (i, e; arg)
|
|
387 new_arg[i] = JSONValue(e);
|
|
388 () @trusted { store.array = new_arg; }();
|
|
389 }
|
|
390 }
|
|
391 else static if (is(T : JSONValue))
|
|
392 {
|
|
393 type_tag = arg.type;
|
|
394 store = arg.store;
|
|
395 }
|
|
396 else
|
|
397 {
|
|
398 static assert(false, text(`unable to convert type "`, T.stringof, `" to json`));
|
|
399 }
|
|
400 }
|
|
401
|
|
402 private void assignRef(T)(ref T arg) if (isStaticArray!T)
|
|
403 {
|
|
404 type_tag = JSON_TYPE.ARRAY;
|
|
405 static if (is(ElementEncodingType!T : JSONValue))
|
|
406 {
|
|
407 store.array = arg;
|
|
408 }
|
|
409 else
|
|
410 {
|
|
411 JSONValue[] new_arg = new JSONValue[arg.length];
|
|
412 foreach (i, e; arg)
|
|
413 new_arg[i] = JSONValue(e);
|
|
414 store.array = new_arg;
|
|
415 }
|
|
416 }
|
|
417
|
|
418 /**
|
|
419 * Constructor for $(D JSONValue). If $(D arg) is a $(D JSONValue)
|
|
420 * its value and type will be copied to the new $(D JSONValue).
|
|
421 * Note that this is a shallow copy: if type is $(D JSON_TYPE.OBJECT)
|
|
422 * or $(D JSON_TYPE.ARRAY) then only the reference to the data will
|
|
423 * be copied.
|
|
424 * Otherwise, $(D arg) must be implicitly convertible to one of the
|
|
425 * following types: $(D typeof(null)), $(D string), $(D ulong),
|
|
426 * $(D long), $(D double), an associative array $(D V[K]) for any $(D V)
|
|
427 * and $(D K) i.e. a JSON object, any array or $(D bool). The type will
|
|
428 * be set accordingly.
|
|
429 */
|
|
430 this(T)(T arg) if (!isStaticArray!T)
|
|
431 {
|
|
432 assign(arg);
|
|
433 }
|
|
434 /// Ditto
|
|
435 this(T)(ref T arg) if (isStaticArray!T)
|
|
436 {
|
|
437 assignRef(arg);
|
|
438 }
|
|
439 /// Ditto
|
|
440 this(T : JSONValue)(inout T arg) inout
|
|
441 {
|
|
442 store = arg.store;
|
|
443 type_tag = arg.type;
|
|
444 }
|
|
445 ///
|
|
446 @safe unittest
|
|
447 {
|
|
448 JSONValue j = JSONValue( "a string" );
|
|
449 j = JSONValue(42);
|
|
450
|
|
451 j = JSONValue( [1, 2, 3] );
|
|
452 assert(j.type == JSON_TYPE.ARRAY);
|
|
453
|
|
454 j = JSONValue( ["language": "D"] );
|
|
455 assert(j.type == JSON_TYPE.OBJECT);
|
|
456 }
|
|
457
|
|
458 void opAssign(T)(T arg) if (!isStaticArray!T && !is(T : JSONValue))
|
|
459 {
|
|
460 assign(arg);
|
|
461 }
|
|
462
|
|
463 void opAssign(T)(ref T arg) if (isStaticArray!T)
|
|
464 {
|
|
465 assignRef(arg);
|
|
466 }
|
|
467
|
|
468 /***
|
|
469 * Array syntax for json arrays.
|
|
470 * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.ARRAY).
|
|
471 */
|
|
472 ref inout(JSONValue) opIndex(size_t i) inout pure @safe
|
|
473 {
|
|
474 auto a = this.arrayNoRef;
|
|
475 enforceEx!JSONException(i < a.length,
|
|
476 "JSONValue array index is out of range");
|
|
477 return a[i];
|
|
478 }
|
|
479 ///
|
|
480 @safe unittest
|
|
481 {
|
|
482 JSONValue j = JSONValue( [42, 43, 44] );
|
|
483 assert( j[0].integer == 42 );
|
|
484 assert( j[1].integer == 43 );
|
|
485 }
|
|
486
|
|
487 /***
|
|
488 * Hash syntax for json objects.
|
|
489 * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT).
|
|
490 */
|
|
491 ref inout(JSONValue) opIndex(string k) inout pure @safe
|
|
492 {
|
|
493 auto o = this.objectNoRef;
|
|
494 return *enforce!JSONException(k in o,
|
|
495 "Key not found: " ~ k);
|
|
496 }
|
|
497 ///
|
|
498 @safe unittest
|
|
499 {
|
|
500 JSONValue j = JSONValue( ["language": "D"] );
|
|
501 assert( j["language"].str == "D" );
|
|
502 }
|
|
503
|
|
504 /***
|
|
505 * Operator sets $(D value) for element of JSON object by $(D key).
|
|
506 *
|
|
507 * If JSON value is null, then operator initializes it with object and then
|
|
508 * sets $(D value) for it.
|
|
509 *
|
|
510 * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT)
|
|
511 * or $(D JSON_TYPE.NULL).
|
|
512 */
|
|
513 void opIndexAssign(T)(auto ref T value, string key) pure
|
|
514 {
|
|
515 enforceEx!JSONException(type == JSON_TYPE.OBJECT || type == JSON_TYPE.NULL,
|
|
516 "JSONValue must be object or null");
|
|
517 JSONValue[string] aa = null;
|
|
518 if (type == JSON_TYPE.OBJECT)
|
|
519 {
|
|
520 aa = this.objectNoRef;
|
|
521 }
|
|
522
|
|
523 aa[key] = value;
|
|
524 this.object = aa;
|
|
525 }
|
|
526 ///
|
|
527 @safe unittest
|
|
528 {
|
|
529 JSONValue j = JSONValue( ["language": "D"] );
|
|
530 j["language"].str = "Perl";
|
|
531 assert( j["language"].str == "Perl" );
|
|
532 }
|
|
533
|
|
534 void opIndexAssign(T)(T arg, size_t i) pure
|
|
535 {
|
|
536 auto a = this.arrayNoRef;
|
|
537 enforceEx!JSONException(i < a.length,
|
|
538 "JSONValue array index is out of range");
|
|
539 a[i] = arg;
|
|
540 this.array = a;
|
|
541 }
|
|
542 ///
|
|
543 @safe unittest
|
|
544 {
|
|
545 JSONValue j = JSONValue( ["Perl", "C"] );
|
|
546 j[1].str = "D";
|
|
547 assert( j[1].str == "D" );
|
|
548 }
|
|
549
|
|
550 JSONValue opBinary(string op : "~", T)(T arg) @safe
|
|
551 {
|
|
552 auto a = this.arrayNoRef;
|
|
553 static if (isArray!T)
|
|
554 {
|
|
555 return JSONValue(a ~ JSONValue(arg).arrayNoRef);
|
|
556 }
|
|
557 else static if (is(T : JSONValue))
|
|
558 {
|
|
559 return JSONValue(a ~ arg.arrayNoRef);
|
|
560 }
|
|
561 else
|
|
562 {
|
|
563 static assert(false, "argument is not an array or a JSONValue array");
|
|
564 }
|
|
565 }
|
|
566
|
|
567 void opOpAssign(string op : "~", T)(T arg) @safe
|
|
568 {
|
|
569 auto a = this.arrayNoRef;
|
|
570 static if (isArray!T)
|
|
571 {
|
|
572 a ~= JSONValue(arg).arrayNoRef;
|
|
573 }
|
|
574 else static if (is(T : JSONValue))
|
|
575 {
|
|
576 a ~= arg.arrayNoRef;
|
|
577 }
|
|
578 else
|
|
579 {
|
|
580 static assert(false, "argument is not an array or a JSONValue array");
|
|
581 }
|
|
582 this.array = a;
|
|
583 }
|
|
584
|
|
585 /**
|
|
586 * Support for the $(D in) operator.
|
|
587 *
|
|
588 * Tests wether a key can be found in an object.
|
|
589 *
|
|
590 * Returns:
|
|
591 * when found, the $(D const(JSONValue)*) that matches to the key,
|
|
592 * otherwise $(D null).
|
|
593 *
|
|
594 * Throws: $(D JSONException) if the right hand side argument $(D JSON_TYPE)
|
|
595 * is not $(D OBJECT).
|
|
596 */
|
|
597 auto opBinaryRight(string op : "in")(string k) const @safe
|
|
598 {
|
|
599 return k in this.objectNoRef;
|
|
600 }
|
|
601 ///
|
|
602 @safe unittest
|
|
603 {
|
|
604 JSONValue j = [ "language": "D", "author": "walter" ];
|
|
605 string a = ("author" in j).str;
|
|
606 }
|
|
607
|
|
608 bool opEquals(const JSONValue rhs) const @nogc nothrow pure @safe
|
|
609 {
|
|
610 return opEquals(rhs);
|
|
611 }
|
|
612
|
|
613 bool opEquals(ref const JSONValue rhs) const @nogc nothrow pure @trusted
|
|
614 {
|
|
615 // Default doesn't work well since store is a union. Compare only
|
|
616 // what should be in store.
|
|
617 // This is @trusted to remain nogc, nothrow, fast, and usable from @safe code.
|
|
618 if (type_tag != rhs.type_tag) return false;
|
|
619
|
|
620 final switch (type_tag)
|
|
621 {
|
|
622 case JSON_TYPE.STRING:
|
|
623 return store.str == rhs.store.str;
|
|
624 case JSON_TYPE.INTEGER:
|
|
625 return store.integer == rhs.store.integer;
|
|
626 case JSON_TYPE.UINTEGER:
|
|
627 return store.uinteger == rhs.store.uinteger;
|
|
628 case JSON_TYPE.FLOAT:
|
|
629 return store.floating == rhs.store.floating;
|
|
630 case JSON_TYPE.OBJECT:
|
|
631 return store.object == rhs.store.object;
|
|
632 case JSON_TYPE.ARRAY:
|
|
633 return store.array == rhs.store.array;
|
|
634 case JSON_TYPE.TRUE:
|
|
635 case JSON_TYPE.FALSE:
|
|
636 case JSON_TYPE.NULL:
|
|
637 return true;
|
|
638 }
|
|
639 }
|
|
640
|
|
641 /// Implements the foreach $(D opApply) interface for json arrays.
|
|
642 int opApply(scope int delegate(size_t index, ref JSONValue) dg) @system
|
|
643 {
|
|
644 int result;
|
|
645
|
|
646 foreach (size_t index, ref value; array)
|
|
647 {
|
|
648 result = dg(index, value);
|
|
649 if (result)
|
|
650 break;
|
|
651 }
|
|
652
|
|
653 return result;
|
|
654 }
|
|
655
|
|
656 /// Implements the foreach $(D opApply) interface for json objects.
|
|
657 int opApply(scope int delegate(string key, ref JSONValue) dg) @system
|
|
658 {
|
|
659 enforce!JSONException(type == JSON_TYPE.OBJECT,
|
|
660 "JSONValue is not an object");
|
|
661 int result;
|
|
662
|
|
663 foreach (string key, ref value; object)
|
|
664 {
|
|
665 result = dg(key, value);
|
|
666 if (result)
|
|
667 break;
|
|
668 }
|
|
669
|
|
670 return result;
|
|
671 }
|
|
672
|
|
673 /***
|
|
674 * Implicitly calls $(D toJSON) on this JSONValue.
|
|
675 *
|
|
676 * $(I options) can be used to tweak the conversion behavior.
|
|
677 */
|
|
678 string toString(in JSONOptions options = JSONOptions.none) const @safe
|
|
679 {
|
|
680 return toJSON(this, false, options);
|
|
681 }
|
|
682
|
|
683 /***
|
|
684 * Implicitly calls $(D toJSON) on this JSONValue, like $(D toString), but
|
|
685 * also passes $(I true) as $(I pretty) argument.
|
|
686 *
|
|
687 * $(I options) can be used to tweak the conversion behavior
|
|
688 */
|
|
689 string toPrettyString(in JSONOptions options = JSONOptions.none) const @safe
|
|
690 {
|
|
691 return toJSON(this, true, options);
|
|
692 }
|
|
693 }
|
|
694
|
|
695 /**
|
|
696 Parses a serialized string and returns a tree of JSON values.
|
|
697 Throws: $(LREF JSONException) if the depth exceeds the max depth.
|
|
698 Params:
|
|
699 json = json-formatted string to parse
|
|
700 maxDepth = maximum depth of nesting allowed, -1 disables depth checking
|
|
701 options = enable decoding string representations of NaN/Inf as float values
|
|
702 */
|
|
703 JSONValue parseJSON(T)(T json, int maxDepth = -1, JSONOptions options = JSONOptions.none)
|
|
704 if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T))
|
|
705 {
|
|
706 import std.ascii : isWhite, isDigit, isHexDigit, toUpper, toLower;
|
|
707 import std.typecons : Yes;
|
|
708 JSONValue root;
|
|
709 root.type_tag = JSON_TYPE.NULL;
|
|
710
|
|
711 // Avoid UTF decoding when possible, as it is unnecessary when
|
|
712 // processing JSON.
|
|
713 static if (is(T : const(char)[]))
|
|
714 alias Char = char;
|
|
715 else
|
|
716 alias Char = Unqual!(ElementType!T);
|
|
717
|
|
718 if (json.empty) return root;
|
|
719
|
|
720 int depth = -1;
|
|
721 Char next = 0;
|
|
722 int line = 1, pos = 0;
|
|
723
|
|
724 void error(string msg)
|
|
725 {
|
|
726 throw new JSONException(msg, line, pos);
|
|
727 }
|
|
728
|
|
729 Char popChar()
|
|
730 {
|
|
731 if (json.empty) error("Unexpected end of data.");
|
|
732 static if (is(T : const(char)[]))
|
|
733 {
|
|
734 Char c = json[0];
|
|
735 json = json[1..$];
|
|
736 }
|
|
737 else
|
|
738 {
|
|
739 Char c = json.front;
|
|
740 json.popFront();
|
|
741 }
|
|
742
|
|
743 if (c == '\n')
|
|
744 {
|
|
745 line++;
|
|
746 pos = 0;
|
|
747 }
|
|
748 else
|
|
749 {
|
|
750 pos++;
|
|
751 }
|
|
752
|
|
753 return c;
|
|
754 }
|
|
755
|
|
756 Char peekChar()
|
|
757 {
|
|
758 if (!next)
|
|
759 {
|
|
760 if (json.empty) return '\0';
|
|
761 next = popChar();
|
|
762 }
|
|
763 return next;
|
|
764 }
|
|
765
|
|
766 void skipWhitespace()
|
|
767 {
|
|
768 while (isWhite(peekChar())) next = 0;
|
|
769 }
|
|
770
|
|
771 Char getChar(bool SkipWhitespace = false)()
|
|
772 {
|
|
773 static if (SkipWhitespace) skipWhitespace();
|
|
774
|
|
775 Char c;
|
|
776 if (next)
|
|
777 {
|
|
778 c = next;
|
|
779 next = 0;
|
|
780 }
|
|
781 else
|
|
782 c = popChar();
|
|
783
|
|
784 return c;
|
|
785 }
|
|
786
|
|
787 void checkChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c)
|
|
788 {
|
|
789 static if (SkipWhitespace) skipWhitespace();
|
|
790 auto c2 = getChar();
|
|
791 static if (!CaseSensitive) c2 = toLower(c2);
|
|
792
|
|
793 if (c2 != c) error(text("Found '", c2, "' when expecting '", c, "'."));
|
|
794 }
|
|
795
|
|
796 bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c)
|
|
797 {
|
|
798 static if (SkipWhitespace) skipWhitespace();
|
|
799 auto c2 = peekChar();
|
|
800 static if (!CaseSensitive) c2 = toLower(c2);
|
|
801
|
|
802 if (c2 != c) return false;
|
|
803
|
|
804 getChar();
|
|
805 return true;
|
|
806 }
|
|
807
|
|
808 wchar parseWChar()
|
|
809 {
|
|
810 wchar val = 0;
|
|
811 foreach_reverse (i; 0 .. 4)
|
|
812 {
|
|
813 auto hex = toUpper(getChar());
|
|
814 if (!isHexDigit(hex)) error("Expecting hex character");
|
|
815 val += (isDigit(hex) ? hex - '0' : hex - ('A' - 10)) << (4 * i);
|
|
816 }
|
|
817 return val;
|
|
818 }
|
|
819
|
|
820 string parseString()
|
|
821 {
|
|
822 import std.ascii : isControl;
|
|
823 import std.uni : isSurrogateHi, isSurrogateLo;
|
|
824 import std.utf : encode, decode;
|
|
825
|
|
826 auto str = appender!string();
|
|
827
|
|
828 Next:
|
|
829 switch (peekChar())
|
|
830 {
|
|
831 case '"':
|
|
832 getChar();
|
|
833 break;
|
|
834
|
|
835 case '\\':
|
|
836 getChar();
|
|
837 auto c = getChar();
|
|
838 switch (c)
|
|
839 {
|
|
840 case '"': str.put('"'); break;
|
|
841 case '\\': str.put('\\'); break;
|
|
842 case '/': str.put('/'); break;
|
|
843 case 'b': str.put('\b'); break;
|
|
844 case 'f': str.put('\f'); break;
|
|
845 case 'n': str.put('\n'); break;
|
|
846 case 'r': str.put('\r'); break;
|
|
847 case 't': str.put('\t'); break;
|
|
848 case 'u':
|
|
849 wchar wc = parseWChar();
|
|
850 dchar val;
|
|
851 // Non-BMP characters are escaped as a pair of
|
|
852 // UTF-16 surrogate characters (see RFC 4627).
|
|
853 if (isSurrogateHi(wc))
|
|
854 {
|
|
855 wchar[2] pair;
|
|
856 pair[0] = wc;
|
|
857 if (getChar() != '\\') error("Expected escaped low surrogate after escaped high surrogate");
|
|
858 if (getChar() != 'u') error("Expected escaped low surrogate after escaped high surrogate");
|
|
859 pair[1] = parseWChar();
|
|
860 size_t index = 0;
|
|
861 val = decode(pair[], index);
|
|
862 if (index != 2) error("Invalid escaped surrogate pair");
|
|
863 }
|
|
864 else
|
|
865 if (isSurrogateLo(wc))
|
|
866 error(text("Unexpected low surrogate"));
|
|
867 else
|
|
868 val = wc;
|
|
869
|
|
870 char[4] buf;
|
|
871 immutable len = encode!(Yes.useReplacementDchar)(buf, val);
|
|
872 str.put(buf[0 .. len]);
|
|
873 break;
|
|
874
|
|
875 default:
|
|
876 error(text("Invalid escape sequence '\\", c, "'."));
|
|
877 }
|
|
878 goto Next;
|
|
879
|
|
880 default:
|
|
881 // RFC 7159 states that control characters U+0000 through
|
|
882 // U+001F must not appear unescaped in a JSON string.
|
|
883 auto c = getChar();
|
|
884 if (isControl(c))
|
|
885 error("Illegal control character.");
|
|
886 str.put(c);
|
|
887 goto Next;
|
|
888 }
|
|
889
|
|
890 return str.data.length ? str.data : "";
|
|
891 }
|
|
892
|
|
893 bool tryGetSpecialFloat(string str, out double val) {
|
|
894 switch (str)
|
|
895 {
|
|
896 case JSONFloatLiteral.nan:
|
|
897 val = double.nan;
|
|
898 return true;
|
|
899 case JSONFloatLiteral.inf:
|
|
900 val = double.infinity;
|
|
901 return true;
|
|
902 case JSONFloatLiteral.negativeInf:
|
|
903 val = -double.infinity;
|
|
904 return true;
|
|
905 default:
|
|
906 return false;
|
|
907 }
|
|
908 }
|
|
909
|
|
910 void parseValue(ref JSONValue value)
|
|
911 {
|
|
912 depth++;
|
|
913
|
|
914 if (maxDepth != -1 && depth > maxDepth) error("Nesting too deep.");
|
|
915
|
|
916 auto c = getChar!true();
|
|
917
|
|
918 switch (c)
|
|
919 {
|
|
920 case '{':
|
|
921 if (testChar('}'))
|
|
922 {
|
|
923 value.object = null;
|
|
924 break;
|
|
925 }
|
|
926
|
|
927 JSONValue[string] obj;
|
|
928 do
|
|
929 {
|
|
930 checkChar('"');
|
|
931 string name = parseString();
|
|
932 checkChar(':');
|
|
933 JSONValue member;
|
|
934 parseValue(member);
|
|
935 obj[name] = member;
|
|
936 }
|
|
937 while (testChar(','));
|
|
938 value.object = obj;
|
|
939
|
|
940 checkChar('}');
|
|
941 break;
|
|
942
|
|
943 case '[':
|
|
944 if (testChar(']'))
|
|
945 {
|
|
946 value.type_tag = JSON_TYPE.ARRAY;
|
|
947 break;
|
|
948 }
|
|
949
|
|
950 JSONValue[] arr;
|
|
951 do
|
|
952 {
|
|
953 JSONValue element;
|
|
954 parseValue(element);
|
|
955 arr ~= element;
|
|
956 }
|
|
957 while (testChar(','));
|
|
958
|
|
959 checkChar(']');
|
|
960 value.array = arr;
|
|
961 break;
|
|
962
|
|
963 case '"':
|
|
964 auto str = parseString();
|
|
965
|
|
966 // if special float parsing is enabled, check if string represents NaN/Inf
|
|
967 if ((options & JSONOptions.specialFloatLiterals) &&
|
|
968 tryGetSpecialFloat(str, value.store.floating))
|
|
969 {
|
|
970 // found a special float, its value was placed in value.store.floating
|
|
971 value.type_tag = JSON_TYPE.FLOAT;
|
|
972 break;
|
|
973 }
|
|
974
|
|
975 value.type_tag = JSON_TYPE.STRING;
|
|
976 value.store.str = str;
|
|
977 break;
|
|
978
|
|
979 case '0': .. case '9':
|
|
980 case '-':
|
|
981 auto number = appender!string();
|
|
982 bool isFloat, isNegative;
|
|
983
|
|
984 void readInteger()
|
|
985 {
|
|
986 if (!isDigit(c)) error("Digit expected");
|
|
987
|
|
988 Next: number.put(c);
|
|
989
|
|
990 if (isDigit(peekChar()))
|
|
991 {
|
|
992 c = getChar();
|
|
993 goto Next;
|
|
994 }
|
|
995 }
|
|
996
|
|
997 if (c == '-')
|
|
998 {
|
|
999 number.put('-');
|
|
1000 c = getChar();
|
|
1001 isNegative = true;
|
|
1002 }
|
|
1003
|
|
1004 readInteger();
|
|
1005
|
|
1006 if (testChar('.'))
|
|
1007 {
|
|
1008 isFloat = true;
|
|
1009 number.put('.');
|
|
1010 c = getChar();
|
|
1011 readInteger();
|
|
1012 }
|
|
1013 if (testChar!(false, false)('e'))
|
|
1014 {
|
|
1015 isFloat = true;
|
|
1016 number.put('e');
|
|
1017 if (testChar('+')) number.put('+');
|
|
1018 else if (testChar('-')) number.put('-');
|
|
1019 c = getChar();
|
|
1020 readInteger();
|
|
1021 }
|
|
1022
|
|
1023 string data = number.data;
|
|
1024 if (isFloat)
|
|
1025 {
|
|
1026 value.type_tag = JSON_TYPE.FLOAT;
|
|
1027 value.store.floating = parse!double(data);
|
|
1028 }
|
|
1029 else
|
|
1030 {
|
|
1031 if (isNegative)
|
|
1032 value.store.integer = parse!long(data);
|
|
1033 else
|
|
1034 value.store.uinteger = parse!ulong(data);
|
|
1035
|
|
1036 value.type_tag = !isNegative && value.store.uinteger & (1UL << 63) ?
|
|
1037 JSON_TYPE.UINTEGER : JSON_TYPE.INTEGER;
|
|
1038 }
|
|
1039 break;
|
|
1040
|
|
1041 case 't':
|
|
1042 case 'T':
|
|
1043 value.type_tag = JSON_TYPE.TRUE;
|
|
1044 checkChar!(false, false)('r');
|
|
1045 checkChar!(false, false)('u');
|
|
1046 checkChar!(false, false)('e');
|
|
1047 break;
|
|
1048
|
|
1049 case 'f':
|
|
1050 case 'F':
|
|
1051 value.type_tag = JSON_TYPE.FALSE;
|
|
1052 checkChar!(false, false)('a');
|
|
1053 checkChar!(false, false)('l');
|
|
1054 checkChar!(false, false)('s');
|
|
1055 checkChar!(false, false)('e');
|
|
1056 break;
|
|
1057
|
|
1058 case 'n':
|
|
1059 case 'N':
|
|
1060 value.type_tag = JSON_TYPE.NULL;
|
|
1061 checkChar!(false, false)('u');
|
|
1062 checkChar!(false, false)('l');
|
|
1063 checkChar!(false, false)('l');
|
|
1064 break;
|
|
1065
|
|
1066 default:
|
|
1067 error(text("Unexpected character '", c, "'."));
|
|
1068 }
|
|
1069
|
|
1070 depth--;
|
|
1071 }
|
|
1072
|
|
1073 parseValue(root);
|
|
1074 return root;
|
|
1075 }
|
|
1076
|
|
1077 @safe unittest
|
|
1078 {
|
|
1079 enum issue15742objectOfObject = `{ "key1": { "key2": 1 }}`;
|
|
1080 static assert(parseJSON(issue15742objectOfObject).type == JSON_TYPE.OBJECT);
|
|
1081
|
|
1082 enum issue15742arrayOfArray = `[[1]]`;
|
|
1083 static assert(parseJSON(issue15742arrayOfArray).type == JSON_TYPE.ARRAY);
|
|
1084 }
|
|
1085
|
|
1086 @safe unittest
|
|
1087 {
|
|
1088 // Ensure we can parse and use JSON from @safe code
|
|
1089 auto a = `{ "key1": { "key2": 1 }}`.parseJSON;
|
|
1090 assert(a["key1"]["key2"].integer == 1);
|
|
1091 assert(a.toString == `{"key1":{"key2":1}}`);
|
|
1092 }
|
|
1093
|
|
1094 @system unittest
|
|
1095 {
|
|
1096 // Ensure we can parse JSON from a @system range.
|
|
1097 struct Range
|
|
1098 {
|
|
1099 string s;
|
|
1100 size_t index;
|
|
1101 @system
|
|
1102 {
|
|
1103 bool empty() { return index >= s.length; }
|
|
1104 void popFront() { index++; }
|
|
1105 char front() { return s[index]; }
|
|
1106 }
|
|
1107 }
|
|
1108 auto s = Range(`{ "key1": { "key2": 1 }}`);
|
|
1109 auto json = parseJSON(s);
|
|
1110 assert(json["key1"]["key2"].integer == 1);
|
|
1111 }
|
|
1112
|
|
1113 /**
|
|
1114 Parses a serialized string and returns a tree of JSON values.
|
|
1115 Throws: $(REF JSONException, std,json) if the depth exceeds the max depth.
|
|
1116 Params:
|
|
1117 json = json-formatted string to parse
|
|
1118 options = enable decoding string representations of NaN/Inf as float values
|
|
1119 */
|
|
1120 JSONValue parseJSON(T)(T json, JSONOptions options)
|
|
1121 if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T))
|
|
1122 {
|
|
1123 return parseJSON!T(json, -1, options);
|
|
1124 }
|
|
1125
|
|
1126 /**
|
|
1127 Takes a tree of JSON values and returns the serialized string.
|
|
1128
|
|
1129 Any Object types will be serialized in a key-sorted order.
|
|
1130
|
|
1131 If $(D pretty) is false no whitespaces are generated.
|
|
1132 If $(D pretty) is true serialized string is formatted to be human-readable.
|
|
1133 Set the $(LREF JSONOptions.specialFloatLiterals) flag is set in $(D options) to encode NaN/Infinity as strings.
|
|
1134 */
|
|
1135 string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions options = JSONOptions.none) @safe
|
|
1136 {
|
|
1137 auto json = appender!string();
|
|
1138
|
|
1139 void toStringImpl(Char)(string str) @safe
|
|
1140 {
|
|
1141 json.put('"');
|
|
1142
|
|
1143 foreach (Char c; str)
|
|
1144 {
|
|
1145 switch (c)
|
|
1146 {
|
|
1147 case '"': json.put("\\\""); break;
|
|
1148 case '\\': json.put("\\\\"); break;
|
|
1149
|
|
1150 case '/':
|
|
1151 if (!(options & JSONOptions.doNotEscapeSlashes))
|
|
1152 json.put('\\');
|
|
1153 json.put('/');
|
|
1154 break;
|
|
1155
|
|
1156 case '\b': json.put("\\b"); break;
|
|
1157 case '\f': json.put("\\f"); break;
|
|
1158 case '\n': json.put("\\n"); break;
|
|
1159 case '\r': json.put("\\r"); break;
|
|
1160 case '\t': json.put("\\t"); break;
|
|
1161 default:
|
|
1162 {
|
|
1163 import std.ascii : isControl;
|
|
1164 import std.utf : encode;
|
|
1165
|
|
1166 // Make sure we do UTF decoding iff we want to
|
|
1167 // escape Unicode characters.
|
|
1168 assert(((options & JSONOptions.escapeNonAsciiChars) != 0)
|
|
1169 == is(Char == dchar));
|
|
1170
|
|
1171 with (JSONOptions) if (isControl(c) ||
|
|
1172 ((options & escapeNonAsciiChars) >= escapeNonAsciiChars && c >= 0x80))
|
|
1173 {
|
|
1174 // Ensure non-BMP characters are encoded as a pair
|
|
1175 // of UTF-16 surrogate characters, as per RFC 4627.
|
|
1176 wchar[2] wchars; // 1 or 2 UTF-16 code units
|
|
1177 size_t wNum = encode(wchars, c); // number of UTF-16 code units
|
|
1178 foreach (wc; wchars[0 .. wNum])
|
|
1179 {
|
|
1180 json.put("\\u");
|
|
1181 foreach_reverse (i; 0 .. 4)
|
|
1182 {
|
|
1183 char ch = (wc >>> (4 * i)) & 0x0f;
|
|
1184 ch += ch < 10 ? '0' : 'A' - 10;
|
|
1185 json.put(ch);
|
|
1186 }
|
|
1187 }
|
|
1188 }
|
|
1189 else
|
|
1190 {
|
|
1191 json.put(c);
|
|
1192 }
|
|
1193 }
|
|
1194 }
|
|
1195 }
|
|
1196
|
|
1197 json.put('"');
|
|
1198 }
|
|
1199
|
|
1200 void toString(string str) @safe
|
|
1201 {
|
|
1202 // Avoid UTF decoding when possible, as it is unnecessary when
|
|
1203 // processing JSON.
|
|
1204 if (options & JSONOptions.escapeNonAsciiChars)
|
|
1205 toStringImpl!dchar(str);
|
|
1206 else
|
|
1207 toStringImpl!char(str);
|
|
1208 }
|
|
1209
|
|
1210 void toValue(ref in JSONValue value, ulong indentLevel) @safe
|
|
1211 {
|
|
1212 void putTabs(ulong additionalIndent = 0)
|
|
1213 {
|
|
1214 if (pretty)
|
|
1215 foreach (i; 0 .. indentLevel + additionalIndent)
|
|
1216 json.put(" ");
|
|
1217 }
|
|
1218 void putEOL()
|
|
1219 {
|
|
1220 if (pretty)
|
|
1221 json.put('\n');
|
|
1222 }
|
|
1223 void putCharAndEOL(char ch)
|
|
1224 {
|
|
1225 json.put(ch);
|
|
1226 putEOL();
|
|
1227 }
|
|
1228
|
|
1229 final switch (value.type)
|
|
1230 {
|
|
1231 case JSON_TYPE.OBJECT:
|
|
1232 auto obj = value.objectNoRef;
|
|
1233 if (!obj.length)
|
|
1234 {
|
|
1235 json.put("{}");
|
|
1236 }
|
|
1237 else
|
|
1238 {
|
|
1239 putCharAndEOL('{');
|
|
1240 bool first = true;
|
|
1241
|
|
1242 void emit(R)(R names)
|
|
1243 {
|
|
1244 foreach (name; names)
|
|
1245 {
|
|
1246 auto member = obj[name];
|
|
1247 if (!first)
|
|
1248 putCharAndEOL(',');
|
|
1249 first = false;
|
|
1250 putTabs(1);
|
|
1251 toString(name);
|
|
1252 json.put(':');
|
|
1253 if (pretty)
|
|
1254 json.put(' ');
|
|
1255 toValue(member, indentLevel + 1);
|
|
1256 }
|
|
1257 }
|
|
1258
|
|
1259 import std.algorithm.sorting : sort;
|
|
1260 // @@@BUG@@@ 14439
|
|
1261 // auto names = obj.keys; // aa.keys can't be called in @safe code
|
|
1262 auto names = new string[obj.length];
|
|
1263 size_t i = 0;
|
|
1264 foreach (k, v; obj)
|
|
1265 {
|
|
1266 names[i] = k;
|
|
1267 i++;
|
|
1268 }
|
|
1269 sort(names);
|
|
1270 emit(names);
|
|
1271
|
|
1272 putEOL();
|
|
1273 putTabs();
|
|
1274 json.put('}');
|
|
1275 }
|
|
1276 break;
|
|
1277
|
|
1278 case JSON_TYPE.ARRAY:
|
|
1279 auto arr = value.arrayNoRef;
|
|
1280 if (arr.empty)
|
|
1281 {
|
|
1282 json.put("[]");
|
|
1283 }
|
|
1284 else
|
|
1285 {
|
|
1286 putCharAndEOL('[');
|
|
1287 foreach (i, el; arr)
|
|
1288 {
|
|
1289 if (i)
|
|
1290 putCharAndEOL(',');
|
|
1291 putTabs(1);
|
|
1292 toValue(el, indentLevel + 1);
|
|
1293 }
|
|
1294 putEOL();
|
|
1295 putTabs();
|
|
1296 json.put(']');
|
|
1297 }
|
|
1298 break;
|
|
1299
|
|
1300 case JSON_TYPE.STRING:
|
|
1301 toString(value.str);
|
|
1302 break;
|
|
1303
|
|
1304 case JSON_TYPE.INTEGER:
|
|
1305 json.put(to!string(value.store.integer));
|
|
1306 break;
|
|
1307
|
|
1308 case JSON_TYPE.UINTEGER:
|
|
1309 json.put(to!string(value.store.uinteger));
|
|
1310 break;
|
|
1311
|
|
1312 case JSON_TYPE.FLOAT:
|
|
1313 import std.math : isNaN, isInfinity;
|
|
1314
|
|
1315 auto val = value.store.floating;
|
|
1316
|
|
1317 if (val.isNaN)
|
|
1318 {
|
|
1319 if (options & JSONOptions.specialFloatLiterals)
|
|
1320 {
|
|
1321 toString(JSONFloatLiteral.nan);
|
|
1322 }
|
|
1323 else
|
|
1324 {
|
|
1325 throw new JSONException(
|
|
1326 "Cannot encode NaN. Consider passing the specialFloatLiterals flag.");
|
|
1327 }
|
|
1328 }
|
|
1329 else if (val.isInfinity)
|
|
1330 {
|
|
1331 if (options & JSONOptions.specialFloatLiterals)
|
|
1332 {
|
|
1333 toString((val > 0) ? JSONFloatLiteral.inf : JSONFloatLiteral.negativeInf);
|
|
1334 }
|
|
1335 else
|
|
1336 {
|
|
1337 throw new JSONException(
|
|
1338 "Cannot encode Infinity. Consider passing the specialFloatLiterals flag.");
|
|
1339 }
|
|
1340 }
|
|
1341 else
|
|
1342 {
|
|
1343 import std.format : format;
|
|
1344 // The correct formula for the number of decimal digits needed for lossless round
|
|
1345 // trips is actually:
|
|
1346 // ceil(log(pow(2.0, double.mant_dig - 1)) / log(10.0) + 1) == (double.dig + 2)
|
|
1347 // Anything less will round off (1 + double.epsilon)
|
|
1348 json.put("%.18g".format(val));
|
|
1349 }
|
|
1350 break;
|
|
1351
|
|
1352 case JSON_TYPE.TRUE:
|
|
1353 json.put("true");
|
|
1354 break;
|
|
1355
|
|
1356 case JSON_TYPE.FALSE:
|
|
1357 json.put("false");
|
|
1358 break;
|
|
1359
|
|
1360 case JSON_TYPE.NULL:
|
|
1361 json.put("null");
|
|
1362 break;
|
|
1363 }
|
|
1364 }
|
|
1365
|
|
1366 toValue(root, 0);
|
|
1367 return json.data;
|
|
1368 }
|
|
1369
|
|
1370 @safe unittest // bugzilla 12897
|
|
1371 {
|
|
1372 JSONValue jv0 = JSONValue("test测试");
|
|
1373 assert(toJSON(jv0, false, JSONOptions.escapeNonAsciiChars) == `"test\u6D4B\u8BD5"`);
|
|
1374 JSONValue jv00 = JSONValue("test\u6D4B\u8BD5");
|
|
1375 assert(toJSON(jv00, false, JSONOptions.none) == `"test测试"`);
|
|
1376 assert(toJSON(jv0, false, JSONOptions.none) == `"test测试"`);
|
|
1377 JSONValue jv1 = JSONValue("été");
|
|
1378 assert(toJSON(jv1, false, JSONOptions.escapeNonAsciiChars) == `"\u00E9t\u00E9"`);
|
|
1379 JSONValue jv11 = JSONValue("\u00E9t\u00E9");
|
|
1380 assert(toJSON(jv11, false, JSONOptions.none) == `"été"`);
|
|
1381 assert(toJSON(jv1, false, JSONOptions.none) == `"été"`);
|
|
1382 }
|
|
1383
|
|
1384 /**
|
|
1385 Exception thrown on JSON errors
|
|
1386 */
|
|
1387 class JSONException : Exception
|
|
1388 {
|
|
1389 this(string msg, int line = 0, int pos = 0) pure nothrow @safe
|
|
1390 {
|
|
1391 if (line)
|
|
1392 super(text(msg, " (Line ", line, ":", pos, ")"));
|
|
1393 else
|
|
1394 super(msg);
|
|
1395 }
|
|
1396
|
|
1397 this(string msg, string file, size_t line) pure nothrow @safe
|
|
1398 {
|
|
1399 super(msg, file, line);
|
|
1400 }
|
|
1401 }
|
|
1402
|
|
1403
|
|
1404 @system unittest
|
|
1405 {
|
|
1406 import std.exception;
|
|
1407 JSONValue jv = "123";
|
|
1408 assert(jv.type == JSON_TYPE.STRING);
|
|
1409 assertNotThrown(jv.str);
|
|
1410 assertThrown!JSONException(jv.integer);
|
|
1411 assertThrown!JSONException(jv.uinteger);
|
|
1412 assertThrown!JSONException(jv.floating);
|
|
1413 assertThrown!JSONException(jv.object);
|
|
1414 assertThrown!JSONException(jv.array);
|
|
1415 assertThrown!JSONException(jv["aa"]);
|
|
1416 assertThrown!JSONException(jv[2]);
|
|
1417
|
|
1418 jv = -3;
|
|
1419 assert(jv.type == JSON_TYPE.INTEGER);
|
|
1420 assertNotThrown(jv.integer);
|
|
1421
|
|
1422 jv = cast(uint) 3;
|
|
1423 assert(jv.type == JSON_TYPE.UINTEGER);
|
|
1424 assertNotThrown(jv.uinteger);
|
|
1425
|
|
1426 jv = 3.0;
|
|
1427 assert(jv.type == JSON_TYPE.FLOAT);
|
|
1428 assertNotThrown(jv.floating);
|
|
1429
|
|
1430 jv = ["key" : "value"];
|
|
1431 assert(jv.type == JSON_TYPE.OBJECT);
|
|
1432 assertNotThrown(jv.object);
|
|
1433 assertNotThrown(jv["key"]);
|
|
1434 assert("key" in jv);
|
|
1435 assert("notAnElement" !in jv);
|
|
1436 assertThrown!JSONException(jv["notAnElement"]);
|
|
1437 const cjv = jv;
|
|
1438 assert("key" in cjv);
|
|
1439 assertThrown!JSONException(cjv["notAnElement"]);
|
|
1440
|
|
1441 foreach (string key, value; jv)
|
|
1442 {
|
|
1443 static assert(is(typeof(value) == JSONValue));
|
|
1444 assert(key == "key");
|
|
1445 assert(value.type == JSON_TYPE.STRING);
|
|
1446 assertNotThrown(value.str);
|
|
1447 assert(value.str == "value");
|
|
1448 }
|
|
1449
|
|
1450 jv = [3, 4, 5];
|
|
1451 assert(jv.type == JSON_TYPE.ARRAY);
|
|
1452 assertNotThrown(jv.array);
|
|
1453 assertNotThrown(jv[2]);
|
|
1454 foreach (size_t index, value; jv)
|
|
1455 {
|
|
1456 static assert(is(typeof(value) == JSONValue));
|
|
1457 assert(value.type == JSON_TYPE.INTEGER);
|
|
1458 assertNotThrown(value.integer);
|
|
1459 assert(index == (value.integer-3));
|
|
1460 }
|
|
1461
|
|
1462 jv = null;
|
|
1463 assert(jv.type == JSON_TYPE.NULL);
|
|
1464 assert(jv.isNull);
|
|
1465 jv = "foo";
|
|
1466 assert(!jv.isNull);
|
|
1467
|
|
1468 jv = JSONValue("value");
|
|
1469 assert(jv.type == JSON_TYPE.STRING);
|
|
1470 assert(jv.str == "value");
|
|
1471
|
|
1472 JSONValue jv2 = JSONValue("value");
|
|
1473 assert(jv2.type == JSON_TYPE.STRING);
|
|
1474 assert(jv2.str == "value");
|
|
1475
|
|
1476 JSONValue jv3 = JSONValue("\u001c");
|
|
1477 assert(jv3.type == JSON_TYPE.STRING);
|
|
1478 assert(jv3.str == "\u001C");
|
|
1479 }
|
|
1480
|
|
1481 @system unittest
|
|
1482 {
|
|
1483 // Bugzilla 11504
|
|
1484
|
|
1485 JSONValue jv = 1;
|
|
1486 assert(jv.type == JSON_TYPE.INTEGER);
|
|
1487
|
|
1488 jv.str = "123";
|
|
1489 assert(jv.type == JSON_TYPE.STRING);
|
|
1490 assert(jv.str == "123");
|
|
1491
|
|
1492 jv.integer = 1;
|
|
1493 assert(jv.type == JSON_TYPE.INTEGER);
|
|
1494 assert(jv.integer == 1);
|
|
1495
|
|
1496 jv.uinteger = 2u;
|
|
1497 assert(jv.type == JSON_TYPE.UINTEGER);
|
|
1498 assert(jv.uinteger == 2u);
|
|
1499
|
|
1500 jv.floating = 1.5;
|
|
1501 assert(jv.type == JSON_TYPE.FLOAT);
|
|
1502 assert(jv.floating == 1.5);
|
|
1503
|
|
1504 jv.object = ["key" : JSONValue("value")];
|
|
1505 assert(jv.type == JSON_TYPE.OBJECT);
|
|
1506 assert(jv.object == ["key" : JSONValue("value")]);
|
|
1507
|
|
1508 jv.array = [JSONValue(1), JSONValue(2), JSONValue(3)];
|
|
1509 assert(jv.type == JSON_TYPE.ARRAY);
|
|
1510 assert(jv.array == [JSONValue(1), JSONValue(2), JSONValue(3)]);
|
|
1511
|
|
1512 jv = true;
|
|
1513 assert(jv.type == JSON_TYPE.TRUE);
|
|
1514
|
|
1515 jv = false;
|
|
1516 assert(jv.type == JSON_TYPE.FALSE);
|
|
1517
|
|
1518 enum E{True = true}
|
|
1519 jv = E.True;
|
|
1520 assert(jv.type == JSON_TYPE.TRUE);
|
|
1521 }
|
|
1522
|
|
1523 @system pure unittest
|
|
1524 {
|
|
1525 // Adding new json element via array() / object() directly
|
|
1526
|
|
1527 JSONValue jarr = JSONValue([10]);
|
|
1528 foreach (i; 0 .. 9)
|
|
1529 jarr.array ~= JSONValue(i);
|
|
1530 assert(jarr.array.length == 10);
|
|
1531
|
|
1532 JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
|
|
1533 foreach (i; 0 .. 9)
|
|
1534 jobj.object[text("key", i)] = JSONValue(text("value", i));
|
|
1535 assert(jobj.object.length == 10);
|
|
1536 }
|
|
1537
|
|
1538 @system pure unittest
|
|
1539 {
|
|
1540 // Adding new json element without array() / object() access
|
|
1541
|
|
1542 JSONValue jarr = JSONValue([10]);
|
|
1543 foreach (i; 0 .. 9)
|
|
1544 jarr ~= [JSONValue(i)];
|
|
1545 assert(jarr.array.length == 10);
|
|
1546
|
|
1547 JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
|
|
1548 foreach (i; 0 .. 9)
|
|
1549 jobj[text("key", i)] = JSONValue(text("value", i));
|
|
1550 assert(jobj.object.length == 10);
|
|
1551
|
|
1552 // No array alias
|
|
1553 auto jarr2 = jarr ~ [1,2,3];
|
|
1554 jarr2[0] = 999;
|
|
1555 assert(jarr[0] == JSONValue(10));
|
|
1556 }
|
|
1557
|
|
1558 @system unittest
|
|
1559 {
|
|
1560 // @system because JSONValue.array is @system
|
|
1561 import std.exception;
|
|
1562
|
|
1563 // An overly simple test suite, if it can parse a serializated string and
|
|
1564 // then use the resulting values tree to generate an identical
|
|
1565 // serialization, both the decoder and encoder works.
|
|
1566
|
|
1567 auto jsons = [
|
|
1568 `null`,
|
|
1569 `true`,
|
|
1570 `false`,
|
|
1571 `0`,
|
|
1572 `123`,
|
|
1573 `-4321`,
|
|
1574 `0.25`,
|
|
1575 `-0.25`,
|
|
1576 `""`,
|
|
1577 `"hello\nworld"`,
|
|
1578 `"\"\\\/\b\f\n\r\t"`,
|
|
1579 `[]`,
|
|
1580 `[12,"foo",true,false]`,
|
|
1581 `{}`,
|
|
1582 `{"a":1,"b":null}`,
|
|
1583 `{"goodbye":[true,"or",false,["test",42,{"nested":{"a":23.5,"b":0.140625}}]],`
|
|
1584 ~`"hello":{"array":[12,null,{}],"json":"is great"}}`,
|
|
1585 ];
|
|
1586
|
|
1587 enum dbl1_844 = `1.8446744073709568`;
|
|
1588 version (MinGW)
|
|
1589 jsons ~= dbl1_844 ~ `e+019`;
|
|
1590 else
|
|
1591 jsons ~= dbl1_844 ~ `e+19`;
|
|
1592
|
|
1593 JSONValue val;
|
|
1594 string result;
|
|
1595 foreach (json; jsons)
|
|
1596 {
|
|
1597 try
|
|
1598 {
|
|
1599 val = parseJSON(json);
|
|
1600 enum pretty = false;
|
|
1601 result = toJSON(val, pretty);
|
|
1602 assert(result == json, text(result, " should be ", json));
|
|
1603 }
|
|
1604 catch (JSONException e)
|
|
1605 {
|
|
1606 import std.stdio : writefln;
|
|
1607 writefln(text(json, "\n", e.toString()));
|
|
1608 }
|
|
1609 }
|
|
1610
|
|
1611 // Should be able to correctly interpret unicode entities
|
|
1612 val = parseJSON(`"\u003C\u003E"`);
|
|
1613 assert(toJSON(val) == "\"\<\>\"");
|
|
1614 assert(val.to!string() == "\"\<\>\"");
|
|
1615 val = parseJSON(`"\u0391\u0392\u0393"`);
|
|
1616 assert(toJSON(val) == "\"\Α\Β\Γ\"");
|
|
1617 assert(val.to!string() == "\"\Α\Β\Γ\"");
|
|
1618 val = parseJSON(`"\u2660\u2666"`);
|
|
1619 assert(toJSON(val) == "\"\♠\♦\"");
|
|
1620 assert(val.to!string() == "\"\♠\♦\"");
|
|
1621
|
|
1622 //0x7F is a control character (see Unicode spec)
|
|
1623 val = parseJSON(`"\u007F"`);
|
|
1624 assert(toJSON(val) == "\"\\u007F\"");
|
|
1625 assert(val.to!string() == "\"\\u007F\"");
|
|
1626
|
|
1627 with(parseJSON(`""`))
|
|
1628 assert(str == "" && str !is null);
|
|
1629 with(parseJSON(`[]`))
|
|
1630 assert(!array.length);
|
|
1631
|
|
1632 // Formatting
|
|
1633 val = parseJSON(`{"a":[null,{"x":1},{},[]]}`);
|
|
1634 assert(toJSON(val, true) == `{
|
|
1635 "a": [
|
|
1636 null,
|
|
1637 {
|
|
1638 "x": 1
|
|
1639 },
|
|
1640 {},
|
|
1641 []
|
|
1642 ]
|
|
1643 }`);
|
|
1644 }
|
|
1645
|
|
1646 @safe unittest
|
|
1647 {
|
|
1648 auto json = `"hello\nworld"`;
|
|
1649 const jv = parseJSON(json);
|
|
1650 assert(jv.toString == json);
|
|
1651 assert(jv.toPrettyString == json);
|
|
1652 }
|
|
1653
|
|
1654 @system pure unittest
|
|
1655 {
|
|
1656 // Bugzilla 12969
|
|
1657
|
|
1658 JSONValue jv;
|
|
1659 jv["int"] = 123;
|
|
1660
|
|
1661 assert(jv.type == JSON_TYPE.OBJECT);
|
|
1662 assert("int" in jv);
|
|
1663 assert(jv["int"].integer == 123);
|
|
1664
|
|
1665 jv["array"] = [1, 2, 3, 4, 5];
|
|
1666
|
|
1667 assert(jv["array"].type == JSON_TYPE.ARRAY);
|
|
1668 assert(jv["array"][2].integer == 3);
|
|
1669
|
|
1670 jv["str"] = "D language";
|
|
1671 assert(jv["str"].type == JSON_TYPE.STRING);
|
|
1672 assert(jv["str"].str == "D language");
|
|
1673
|
|
1674 jv["bool"] = false;
|
|
1675 assert(jv["bool"].type == JSON_TYPE.FALSE);
|
|
1676
|
|
1677 assert(jv.object.length == 4);
|
|
1678
|
|
1679 jv = [5, 4, 3, 2, 1];
|
|
1680 assert( jv.type == JSON_TYPE.ARRAY );
|
|
1681 assert( jv[3].integer == 2 );
|
|
1682 }
|
|
1683
|
|
1684 @safe unittest
|
|
1685 {
|
|
1686 auto s = q"EOF
|
|
1687 [
|
|
1688 1,
|
|
1689 2,
|
|
1690 3,
|
|
1691 potato
|
|
1692 ]
|
|
1693 EOF";
|
|
1694
|
|
1695 import std.exception;
|
|
1696
|
|
1697 auto e = collectException!JSONException(parseJSON(s));
|
|
1698 assert(e.msg == "Unexpected character 'p'. (Line 5:3)", e.msg);
|
|
1699 }
|
|
1700
|
|
1701 // handling of special float values (NaN, Inf, -Inf)
|
|
1702 @safe unittest
|
|
1703 {
|
|
1704 import std.exception : assertThrown;
|
|
1705 import std.math : isNaN, isInfinity;
|
|
1706
|
|
1707 // expected representations of NaN and Inf
|
|
1708 enum {
|
|
1709 nanString = '"' ~ JSONFloatLiteral.nan ~ '"',
|
|
1710 infString = '"' ~ JSONFloatLiteral.inf ~ '"',
|
|
1711 negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"',
|
|
1712 }
|
|
1713
|
|
1714 // with the specialFloatLiterals option, encode NaN/Inf as strings
|
|
1715 assert(JSONValue(float.nan).toString(JSONOptions.specialFloatLiterals) == nanString);
|
|
1716 assert(JSONValue(double.infinity).toString(JSONOptions.specialFloatLiterals) == infString);
|
|
1717 assert(JSONValue(-real.infinity).toString(JSONOptions.specialFloatLiterals) == negativeInfString);
|
|
1718
|
|
1719 // without the specialFloatLiterals option, throw on encoding NaN/Inf
|
|
1720 assertThrown!JSONException(JSONValue(float.nan).toString);
|
|
1721 assertThrown!JSONException(JSONValue(double.infinity).toString);
|
|
1722 assertThrown!JSONException(JSONValue(-real.infinity).toString);
|
|
1723
|
|
1724 // when parsing json with specialFloatLiterals option, decode special strings as floats
|
|
1725 JSONValue jvNan = parseJSON(nanString, JSONOptions.specialFloatLiterals);
|
|
1726 JSONValue jvInf = parseJSON(infString, JSONOptions.specialFloatLiterals);
|
|
1727 JSONValue jvNegInf = parseJSON(negativeInfString, JSONOptions.specialFloatLiterals);
|
|
1728
|
|
1729 assert(jvNan.floating.isNaN);
|
|
1730 assert(jvInf.floating.isInfinity && jvInf.floating > 0);
|
|
1731 assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0);
|
|
1732
|
|
1733 // when parsing json without the specialFloatLiterals option, decode special strings as strings
|
|
1734 jvNan = parseJSON(nanString);
|
|
1735 jvInf = parseJSON(infString);
|
|
1736 jvNegInf = parseJSON(negativeInfString);
|
|
1737
|
|
1738 assert(jvNan.str == JSONFloatLiteral.nan);
|
|
1739 assert(jvInf.str == JSONFloatLiteral.inf);
|
|
1740 assert(jvNegInf.str == JSONFloatLiteral.negativeInf);
|
|
1741 }
|
|
1742
|
|
1743 pure nothrow @safe @nogc unittest
|
|
1744 {
|
|
1745 JSONValue testVal;
|
|
1746 testVal = "test";
|
|
1747 testVal = 10;
|
|
1748 testVal = 10u;
|
|
1749 testVal = 1.0;
|
|
1750 testVal = (JSONValue[string]).init;
|
|
1751 testVal = JSONValue[].init;
|
|
1752 testVal = null;
|
|
1753 assert(testVal.isNull);
|
|
1754 }
|
|
1755
|
|
1756 pure nothrow @safe unittest // issue 15884
|
|
1757 {
|
|
1758 import std.typecons;
|
|
1759 void Test(C)() {
|
|
1760 C[] a = ['x'];
|
|
1761 JSONValue testVal = a;
|
|
1762 assert(testVal.type == JSON_TYPE.STRING);
|
|
1763 testVal = a.idup;
|
|
1764 assert(testVal.type == JSON_TYPE.STRING);
|
|
1765 }
|
|
1766 Test!char();
|
|
1767 Test!wchar();
|
|
1768 Test!dchar();
|
|
1769 }
|
|
1770
|
|
1771 @safe unittest // issue 15885
|
|
1772 {
|
|
1773 enum bool realInDoublePrecision = real.mant_dig == double.mant_dig;
|
|
1774
|
|
1775 static bool test(const double num0)
|
|
1776 {
|
|
1777 import std.math : feqrel;
|
|
1778 const json0 = JSONValue(num0);
|
|
1779 const num1 = to!double(toJSON(json0));
|
|
1780 static if (realInDoublePrecision)
|
|
1781 return feqrel(num1, num0) >= (double.mant_dig - 1);
|
|
1782 else
|
|
1783 return num1 == num0;
|
|
1784 }
|
|
1785
|
|
1786 assert(test( 0.23));
|
|
1787 assert(test(-0.23));
|
|
1788 assert(test(1.223e+24));
|
|
1789 assert(test(23.4));
|
|
1790 assert(test(0.0012));
|
|
1791 assert(test(30738.22));
|
|
1792
|
|
1793 assert(test(1 + double.epsilon));
|
|
1794 assert(test(double.min_normal));
|
|
1795 static if (realInDoublePrecision)
|
|
1796 assert(test(-double.max / 2));
|
|
1797 else
|
|
1798 assert(test(-double.max));
|
|
1799
|
|
1800 const minSub = double.min_normal * double.epsilon;
|
|
1801 assert(test(minSub));
|
|
1802 assert(test(3*minSub));
|
|
1803 }
|
|
1804
|
|
1805 @safe unittest // issue 17555
|
|
1806 {
|
|
1807 import std.exception : assertThrown;
|
|
1808
|
|
1809 assertThrown!JSONException(parseJSON("\"a\nb\""));
|
|
1810 }
|
|
1811
|
|
1812 @safe unittest // issue 17556
|
|
1813 {
|
|
1814 auto v = JSONValue("\U0001D11E");
|
|
1815 auto j = toJSON(v, false, JSONOptions.escapeNonAsciiChars);
|
|
1816 assert(j == `"\uD834\uDD1E"`);
|
|
1817 }
|
|
1818
|
|
1819 @safe unittest // issue 5904
|
|
1820 {
|
|
1821 string s = `"\uD834\uDD1E"`;
|
|
1822 auto j = parseJSON(s);
|
|
1823 assert(j.str == "\U0001D11E");
|
|
1824 }
|
|
1825
|
|
1826 @safe unittest // issue 17557
|
|
1827 {
|
|
1828 assert(parseJSON("\"\xFF\"").str == "\xFF");
|
|
1829 assert(parseJSON("\"\U0001D11E\"").str == "\U0001D11E");
|
|
1830 }
|
|
1831
|
|
1832 @safe unittest // issue 17553
|
|
1833 {
|
|
1834 auto v = JSONValue("\xFF");
|
|
1835 assert(toJSON(v) == "\"\xFF\"");
|
|
1836 }
|
|
1837
|
|
1838 @safe unittest
|
|
1839 {
|
|
1840 import std.utf;
|
|
1841 assert(parseJSON("\"\xFF\"".byChar).str == "\xFF");
|
|
1842 assert(parseJSON("\"\U0001D11E\"".byChar).str == "\U0001D11E");
|
|
1843 }
|
|
1844
|
|
1845 @safe unittest // JSONOptions.doNotEscapeSlashes (issue 17587)
|
|
1846 {
|
|
1847 assert(parseJSON(`"/"`).toString == `"\/"`);
|
|
1848 assert(parseJSON(`"\/"`).toString == `"\/"`);
|
|
1849 assert(parseJSON(`"/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
|
|
1850 assert(parseJSON(`"\/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
|
|
1851 }
|