]> git.ipfire.org Git - thirdparty/gcc.git/blob - libphobos/src/std/json.d
c++/modules: Improve diagnostic when redeclaring builtin in module [PR102345]
[thirdparty/gcc.git] / libphobos / src / std / json.d
1 // Written in the D programming language.
2
3 /**
4 Implements functionality to read and write JavaScript Object Notation values.
5
6 JavaScript Object Notation is a lightweight data interchange format commonly used in web services and configuration files.
7 It's easy for humans to read and write, and it's easy for machines to parse and generate.
8
9 $(RED Warning: While $(LREF JSONValue) is fine for small-scale use, at the range of hundreds of megabytes it is
10 known to cause and exacerbate GC problems. If you encounter problems, try replacing it with a stream parser. See
11 also $(LINK https://forum.dlang.org/post/dzfyaxypmkdrpakmycjv@forum.dlang.org).)
12
13 Copyright: Copyright Jeremie Pelletier 2008 - 2009.
14 License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
15 Authors: Jeremie Pelletier, David Herberth
16 References: $(LINK http://json.org/), $(LINK http://seriot.ch/parsing_json.html)
17 Source: $(PHOBOSSRC std/json.d)
18 */
19 /*
20 Copyright Jeremie Pelletier 2008 - 2009.
21 Distributed under the Boost Software License, Version 1.0.
22 (See accompanying file LICENSE_1_0.txt or copy at
23 http://www.boost.org/LICENSE_1_0.txt)
24 */
25 module std.json;
26
27 import std.array;
28 import std.conv;
29 import std.range;
30 import std.traits;
31
32 ///
33 @system unittest
34 {
35 import std.conv : to;
36
37 // parse a file or string of json into a usable structure
38 string s = `{ "language": "D", "rating": 3.5, "code": "42" }`;
39 JSONValue j = parseJSON(s);
40 // j and j["language"] return JSONValue,
41 // j["language"].str returns a string
42 assert(j["language"].str == "D");
43 assert(j["rating"].floating == 3.5);
44
45 // check a type
46 long x;
47 if (const(JSONValue)* code = "code" in j)
48 {
49 if (code.type() == JSONType.integer)
50 x = code.integer;
51 else
52 x = to!int(code.str);
53 }
54
55 // create a json struct
56 JSONValue jj = [ "language": "D" ];
57 // rating doesnt exist yet, so use .object to assign
58 jj.object["rating"] = JSONValue(3.5);
59 // create an array to assign to list
60 jj.object["list"] = JSONValue( ["a", "b", "c"] );
61 // list already exists, so .object optional
62 jj["list"].array ~= JSONValue("D");
63
64 string jjStr = `{"language":"D","list":["a","b","c","D"],"rating":3.5}`;
65 assert(jj.toString == jjStr);
66 }
67
68 /**
69 String literals used to represent special float values within JSON strings.
70 */
71 enum JSONFloatLiteral : string
72 {
73 nan = "NaN", /// String representation of floating-point NaN
74 inf = "Infinite", /// String representation of floating-point Infinity
75 negativeInf = "-Infinite", /// String representation of floating-point negative Infinity
76 }
77
78 /**
79 Flags that control how JSON is encoded and parsed.
80 */
81 enum JSONOptions
82 {
83 none, /// Standard parsing and encoding
84 specialFloatLiterals = 0x1, /// Encode NaN and Inf float values as strings
85 escapeNonAsciiChars = 0x2, /// Encode non-ASCII characters with a Unicode escape sequence
86 doNotEscapeSlashes = 0x4, /// Do not escape slashes ('/')
87 strictParsing = 0x8, /// Strictly follow RFC-8259 grammar when parsing
88 }
89
90 /**
91 Enumeration of JSON types
92 */
93 enum JSONType : byte
94 {
95 /// Indicates the type of a `JSONValue`.
96 null_,
97 string, /// ditto
98 integer, /// ditto
99 uinteger, /// ditto
100 float_, /// ditto
101 array, /// ditto
102 object, /// ditto
103 true_, /// ditto
104 false_, /// ditto
105 // FIXME: Find some way to deprecate the enum members below, which does NOT
106 // create lots of spam-like deprecation warnings, which can't be fixed
107 // by the user. See discussion on this issue at
108 // https://forum.dlang.org/post/feudrhtxkaxxscwhhhff@forum.dlang.org
109 /* deprecated("Use .null_") */ NULL = null_,
110 /* deprecated("Use .string") */ STRING = string,
111 /* deprecated("Use .integer") */ INTEGER = integer,
112 /* deprecated("Use .uinteger") */ UINTEGER = uinteger,
113 /* deprecated("Use .float_") */ FLOAT = float_,
114 /* deprecated("Use .array") */ ARRAY = array,
115 /* deprecated("Use .object") */ OBJECT = object,
116 /* deprecated("Use .true_") */ TRUE = true_,
117 /* deprecated("Use .false_") */ FALSE = false_,
118 }
119
120 deprecated("Use JSONType and the new enum member names") alias JSON_TYPE = JSONType;
121
122 /**
123 JSON value node
124 */
125 struct JSONValue
126 {
127 import std.exception : enforce;
128
129 union Store
130 {
131 string str;
132 long integer;
133 ulong uinteger;
134 double floating;
135 JSONValue[string] object;
136 JSONValue[] array;
137 }
138 private Store store;
139 private JSONType type_tag;
140
141 /**
142 Returns the JSONType of the value stored in this structure.
143 */
144 @property JSONType type() const pure nothrow @safe @nogc
145 {
146 return type_tag;
147 }
148 ///
149 @safe unittest
150 {
151 string s = "{ \"language\": \"D\" }";
152 JSONValue j = parseJSON(s);
153 assert(j.type == JSONType.object);
154 assert(j["language"].type == JSONType.string);
155 }
156
157 /***
158 * Value getter/setter for `JSONType.string`.
159 * Throws: `JSONException` for read access if `type` is not
160 * `JSONType.string`.
161 */
162 @property string str() const pure @trusted return scope
163 {
164 enforce!JSONException(type == JSONType.string,
165 "JSONValue is not a string");
166 return store.str;
167 }
168 /// ditto
169 @property string str(return scope string v) pure nothrow @nogc @trusted return // TODO make @safe
170 {
171 assign(v);
172 return v;
173 }
174 ///
175 @safe unittest
176 {
177 JSONValue j = [ "language": "D" ];
178
179 // get value
180 assert(j["language"].str == "D");
181
182 // change existing key to new string
183 j["language"].str = "Perl";
184 assert(j["language"].str == "Perl");
185 }
186
187 /***
188 * Value getter/setter for `JSONType.integer`.
189 * Throws: `JSONException` for read access if `type` is not
190 * `JSONType.integer`.
191 */
192 @property long integer() const pure @safe
193 {
194 enforce!JSONException(type == JSONType.integer,
195 "JSONValue is not an integer");
196 return store.integer;
197 }
198 /// ditto
199 @property long integer(long v) pure nothrow @safe @nogc
200 {
201 assign(v);
202 return store.integer;
203 }
204
205 /***
206 * Value getter/setter for `JSONType.uinteger`.
207 * Throws: `JSONException` for read access if `type` is not
208 * `JSONType.uinteger`.
209 */
210 @property ulong uinteger() const pure @safe
211 {
212 enforce!JSONException(type == JSONType.uinteger,
213 "JSONValue is not an unsigned integer");
214 return store.uinteger;
215 }
216 /// ditto
217 @property ulong uinteger(ulong v) pure nothrow @safe @nogc
218 {
219 assign(v);
220 return store.uinteger;
221 }
222
223 /***
224 * Value getter/setter for `JSONType.float_`. Note that despite
225 * the name, this is a $(B 64)-bit `double`, not a 32-bit `float`.
226 * Throws: `JSONException` for read access if `type` is not
227 * `JSONType.float_`.
228 */
229 @property double floating() const pure @safe
230 {
231 enforce!JSONException(type == JSONType.float_,
232 "JSONValue is not a floating type");
233 return store.floating;
234 }
235 /// ditto
236 @property double floating(double v) pure nothrow @safe @nogc
237 {
238 assign(v);
239 return store.floating;
240 }
241
242 /***
243 * Value getter/setter for boolean stored in JSON.
244 * Throws: `JSONException` for read access if `this.type` is not
245 * `JSONType.true_` or `JSONType.false_`.
246 */
247 @property bool boolean() const pure @safe
248 {
249 if (type == JSONType.true_) return true;
250 if (type == JSONType.false_) return false;
251
252 throw new JSONException("JSONValue is not a boolean type");
253 }
254 /// ditto
255 @property bool boolean(bool v) pure nothrow @safe @nogc
256 {
257 assign(v);
258 return v;
259 }
260 ///
261 @safe unittest
262 {
263 JSONValue j = true;
264 assert(j.boolean == true);
265
266 j.boolean = false;
267 assert(j.boolean == false);
268
269 j.integer = 12;
270 import std.exception : assertThrown;
271 assertThrown!JSONException(j.boolean);
272 }
273
274 /***
275 * Value getter/setter for `JSONType.object`.
276 * Throws: `JSONException` for read access if `type` is not
277 * `JSONType.object`.
278 * Note: This is @system because of the following pattern:
279 ---
280 auto a = &(json.object());
281 json.uinteger = 0; // overwrite AA pointer
282 (*a)["hello"] = "world"; // segmentation fault
283 ---
284 */
285 @property ref inout(JSONValue[string]) object() inout pure @system return
286 {
287 enforce!JSONException(type == JSONType.object,
288 "JSONValue is not an object");
289 return store.object;
290 }
291 /// ditto
292 @property JSONValue[string] object(return scope JSONValue[string] v) pure nothrow @nogc @trusted // TODO make @safe
293 {
294 assign(v);
295 return v;
296 }
297
298 /***
299 * Value getter for `JSONType.object`.
300 * Unlike `object`, this retrieves the object by value
301 * and can be used in @safe code.
302 *
303 * One possible caveat is that, if the returned value is null,
304 * modifications will not be visible:
305 * ---
306 * JSONValue json;
307 * json.object = null;
308 * json.objectNoRef["hello"] = JSONValue("world");
309 * assert("hello" !in json.object);
310 * ---
311 *
312 * Throws: `JSONException` for read access if `type` is not
313 * `JSONType.object`.
314 */
315 @property inout(JSONValue[string]) objectNoRef() inout pure @trusted
316 {
317 enforce!JSONException(type == JSONType.object,
318 "JSONValue is not an object");
319 return store.object;
320 }
321
322 /***
323 * Value getter/setter for `JSONType.array`.
324 * Throws: `JSONException` for read access if `type` is not
325 * `JSONType.array`.
326 * Note: This is @system because of the following pattern:
327 ---
328 auto a = &(json.array());
329 json.uinteger = 0; // overwrite array pointer
330 (*a)[0] = "world"; // segmentation fault
331 ---
332 */
333 @property ref inout(JSONValue[]) array() scope return inout pure @system
334 {
335 enforce!JSONException(type == JSONType.array,
336 "JSONValue is not an array");
337 return store.array;
338 }
339 /// ditto
340 @property JSONValue[] array(return scope JSONValue[] v) pure nothrow @nogc @trusted scope // TODO make @safe
341 {
342 assign(v);
343 return v;
344 }
345
346 /***
347 * Value getter for `JSONType.array`.
348 * Unlike `array`, this retrieves the array by value and can be used in @safe code.
349 *
350 * One possible caveat is that, if you append to the returned array,
351 * the new values aren't visible in the `JSONValue`:
352 * ---
353 * JSONValue json;
354 * json.array = [JSONValue("hello")];
355 * json.arrayNoRef ~= JSONValue("world");
356 * assert(json.array.length == 1);
357 * ---
358 *
359 * Throws: `JSONException` for read access if `type` is not
360 * `JSONType.array`.
361 */
362 @property inout(JSONValue[]) arrayNoRef() inout pure @trusted
363 {
364 enforce!JSONException(type == JSONType.array,
365 "JSONValue is not an array");
366 return store.array;
367 }
368
369 /// Test whether the type is `JSONType.null_`
370 @property bool isNull() const pure nothrow @safe @nogc
371 {
372 return type == JSONType.null_;
373 }
374
375 /***
376 * A convenience getter that returns this `JSONValue` as the specified D type.
377 * Note: Only numeric types, `bool`, `string`, `JSONValue[string]`, and `JSONValue[]` types are accepted
378 * Throws: `JSONException` if `T` cannot hold the contents of this `JSONValue`
379 * `ConvException` in case of integer overflow when converting to `T`
380 */
381 @property inout(T) get(T)() inout const pure @safe
382 {
383 static if (is(immutable T == immutable string))
384 {
385 return str;
386 }
387 else static if (is(immutable T == immutable bool))
388 {
389 return boolean;
390 }
391 else static if (isFloatingPoint!T)
392 {
393 switch (type)
394 {
395 case JSONType.float_:
396 return cast(T) floating;
397 case JSONType.uinteger:
398 return cast(T) uinteger;
399 case JSONType.integer:
400 return cast(T) integer;
401 default:
402 throw new JSONException("JSONValue is not a number type");
403 }
404 }
405 else static if (isIntegral!T)
406 {
407 switch (type)
408 {
409 case JSONType.uinteger:
410 return uinteger.to!T;
411 case JSONType.integer:
412 return integer.to!T;
413 default:
414 throw new JSONException("JSONValue is not a an integral type");
415 }
416 }
417 else
418 {
419 static assert(false, "Unsupported type");
420 }
421 }
422 // This specialization is needed because arrayNoRef requires inout
423 @property inout(T) get(T : JSONValue[])() inout pure @trusted /// ditto
424 {
425 return arrayNoRef;
426 }
427 /// ditto
428 @property inout(T) get(T : JSONValue[string])() inout pure @trusted
429 {
430 return object;
431 }
432 ///
433 @safe unittest
434 {
435 import std.exception;
436 import std.conv;
437 string s =
438 `{
439 "a": 123,
440 "b": 3.1415,
441 "c": "text",
442 "d": true,
443 "e": [1, 2, 3],
444 "f": { "a": 1 },
445 "g": -45,
446 "h": ` ~ ulong.max.to!string ~ `,
447 }`;
448
449 struct a { }
450
451 immutable json = parseJSON(s);
452 assert(json["a"].get!double == 123.0);
453 assert(json["a"].get!int == 123);
454 assert(json["a"].get!uint == 123);
455 assert(json["b"].get!double == 3.1415);
456 assertThrown!JSONException(json["b"].get!int);
457 assert(json["c"].get!string == "text");
458 assert(json["d"].get!bool == true);
459 assertNotThrown(json["e"].get!(JSONValue[]));
460 assertNotThrown(json["f"].get!(JSONValue[string]));
461 static assert(!__traits(compiles, json["a"].get!a));
462 assertThrown!JSONException(json["e"].get!float);
463 assertThrown!JSONException(json["d"].get!(JSONValue[string]));
464 assertThrown!JSONException(json["f"].get!(JSONValue[]));
465 assert(json["g"].get!int == -45);
466 assertThrown!ConvException(json["g"].get!uint);
467 assert(json["h"].get!ulong == ulong.max);
468 assertThrown!ConvException(json["h"].get!uint);
469 assertNotThrown(json["h"].get!float);
470 }
471
472 private void assign(T)(T arg)
473 {
474 static if (is(T : typeof(null)))
475 {
476 type_tag = JSONType.null_;
477 }
478 else static if (is(T : string))
479 {
480 type_tag = JSONType.string;
481 string t = arg;
482 () @trusted { store.str = t; }();
483 }
484 // https://issues.dlang.org/show_bug.cgi?id=15884
485 else static if (isSomeString!T)
486 {
487 type_tag = JSONType.string;
488 // FIXME: std.Array.Array(Range) is not deduced as 'pure'
489 () @trusted {
490 import std.utf : byUTF;
491 store.str = cast(immutable)(arg.byUTF!char.array);
492 }();
493 }
494 else static if (is(T : bool))
495 {
496 type_tag = arg ? JSONType.true_ : JSONType.false_;
497 }
498 else static if (is(T : ulong) && isUnsigned!T)
499 {
500 type_tag = JSONType.uinteger;
501 store.uinteger = arg;
502 }
503 else static if (is(T : long))
504 {
505 type_tag = JSONType.integer;
506 store.integer = arg;
507 }
508 else static if (isFloatingPoint!T)
509 {
510 type_tag = JSONType.float_;
511 store.floating = arg;
512 }
513 else static if (is(T : Value[Key], Key, Value))
514 {
515 static assert(is(Key : string), "AA key must be string");
516 type_tag = JSONType.object;
517 static if (is(Value : JSONValue))
518 {
519 JSONValue[string] t = arg;
520 () @trusted { store.object = t; }();
521 }
522 else
523 {
524 JSONValue[string] aa;
525 foreach (key, value; arg)
526 aa[key] = JSONValue(value);
527 () @trusted { store.object = aa; }();
528 }
529 }
530 else static if (isArray!T)
531 {
532 type_tag = JSONType.array;
533 static if (is(ElementEncodingType!T : JSONValue))
534 {
535 JSONValue[] t = arg;
536 () @trusted { store.array = t; }();
537 }
538 else
539 {
540 JSONValue[] new_arg = new JSONValue[arg.length];
541 foreach (i, e; arg)
542 new_arg[i] = JSONValue(e);
543 () @trusted { store.array = new_arg; }();
544 }
545 }
546 else static if (is(T : JSONValue))
547 {
548 type_tag = arg.type;
549 store = arg.store;
550 }
551 else
552 {
553 static assert(false, text(`unable to convert type "`, T.stringof, `" to json`));
554 }
555 }
556
557 private void assignRef(T)(ref T arg) if (isStaticArray!T)
558 {
559 type_tag = JSONType.array;
560 static if (is(ElementEncodingType!T : JSONValue))
561 {
562 store.array = arg;
563 }
564 else
565 {
566 JSONValue[] new_arg = new JSONValue[arg.length];
567 foreach (i, e; arg)
568 new_arg[i] = JSONValue(e);
569 store.array = new_arg;
570 }
571 }
572
573 /**
574 * Constructor for `JSONValue`. If `arg` is a `JSONValue`
575 * its value and type will be copied to the new `JSONValue`.
576 * Note that this is a shallow copy: if type is `JSONType.object`
577 * or `JSONType.array` then only the reference to the data will
578 * be copied.
579 * Otherwise, `arg` must be implicitly convertible to one of the
580 * following types: `typeof(null)`, `string`, `ulong`,
581 * `long`, `double`, an associative array `V[K]` for any `V`
582 * and `K` i.e. a JSON object, any array or `bool`. The type will
583 * be set accordingly.
584 */
585 this(T)(T arg) if (!isStaticArray!T)
586 {
587 assign(arg);
588 }
589 /// Ditto
590 this(T)(ref T arg) if (isStaticArray!T)
591 {
592 assignRef(arg);
593 }
594 /// Ditto
595 this(T : JSONValue)(inout T arg) inout
596 {
597 store = arg.store;
598 type_tag = arg.type;
599 }
600 ///
601 @safe unittest
602 {
603 JSONValue j = JSONValue( "a string" );
604 j = JSONValue(42);
605
606 j = JSONValue( [1, 2, 3] );
607 assert(j.type == JSONType.array);
608
609 j = JSONValue( ["language": "D"] );
610 assert(j.type == JSONType.object);
611 }
612
613 /**
614 * An enum value that can be used to obtain a `JSONValue` representing
615 * an empty JSON object.
616 */
617 enum emptyObject = JSONValue(string[string].init);
618 ///
619 @system unittest
620 {
621 JSONValue obj1 = JSONValue.emptyObject;
622 assert(obj1.type == JSONType.object);
623 obj1.object["a"] = JSONValue(1);
624 assert(obj1.object["a"] == JSONValue(1));
625
626 JSONValue obj2 = JSONValue.emptyObject;
627 assert("a" !in obj2.object);
628 obj2.object["b"] = JSONValue(5);
629 assert(obj1 != obj2);
630 }
631
632 /**
633 * An enum value that can be used to obtain a `JSONValue` representing
634 * an empty JSON array.
635 */
636 enum emptyArray = JSONValue(JSONValue[].init);
637 ///
638 @system unittest
639 {
640 JSONValue arr1 = JSONValue.emptyArray;
641 assert(arr1.type == JSONType.array);
642 assert(arr1.array.length == 0);
643 arr1.array ~= JSONValue("Hello");
644 assert(arr1.array.length == 1);
645 assert(arr1.array[0] == JSONValue("Hello"));
646
647 JSONValue arr2 = JSONValue.emptyArray;
648 assert(arr2.array.length == 0);
649 assert(arr1 != arr2);
650 }
651
652 void opAssign(T)(T arg) if (!isStaticArray!T && !is(T : JSONValue))
653 {
654 assign(arg);
655 }
656
657 void opAssign(T)(ref T arg) if (isStaticArray!T)
658 {
659 assignRef(arg);
660 }
661
662 /***
663 * Array syntax for JSON arrays.
664 * Throws: `JSONException` if `type` is not `JSONType.array`.
665 */
666 ref inout(JSONValue) opIndex(size_t i) inout pure @safe
667 {
668 auto a = this.arrayNoRef;
669 enforce!JSONException(i < a.length,
670 "JSONValue array index is out of range");
671 return a[i];
672 }
673 ///
674 @safe unittest
675 {
676 JSONValue j = JSONValue( [42, 43, 44] );
677 assert( j[0].integer == 42 );
678 assert( j[1].integer == 43 );
679 }
680
681 /***
682 * Hash syntax for JSON objects.
683 * Throws: `JSONException` if `type` is not `JSONType.object`.
684 */
685 ref inout(JSONValue) opIndex(return scope string k) inout pure @safe
686 {
687 auto o = this.objectNoRef;
688 return *enforce!JSONException(k in o,
689 "Key not found: " ~ k);
690 }
691 ///
692 @safe unittest
693 {
694 JSONValue j = JSONValue( ["language": "D"] );
695 assert( j["language"].str == "D" );
696 }
697
698 /***
699 * Provides support for index assignments, which sets the
700 * corresponding value of the JSON object's `key` field to `value`.
701 *
702 * If the `JSONValue` is `JSONType.null_`, then this function
703 * initializes it with a JSON object and then performs
704 * the index assignment.
705 *
706 * Throws: `JSONException` if `type` is not `JSONType.object`
707 * or `JSONType.null_`.
708 */
709 void opIndexAssign(T)(auto ref T value, string key)
710 {
711 enforce!JSONException(type == JSONType.object || type == JSONType.null_,
712 "JSONValue must be object or null");
713 JSONValue[string] aa = null;
714 if (type == JSONType.object)
715 {
716 aa = this.objectNoRef;
717 }
718
719 aa[key] = value;
720 this.object = aa;
721 }
722 ///
723 @safe unittest
724 {
725 JSONValue j = JSONValue( ["language": "D"] );
726 j["language"].str = "Perl";
727 assert( j["language"].str == "Perl" );
728 }
729
730 /// ditto
731 void opIndexAssign(T)(T arg, size_t i)
732 {
733 auto a = this.arrayNoRef;
734 enforce!JSONException(i < a.length,
735 "JSONValue array index is out of range");
736 a[i] = arg;
737 this.array = a;
738 }
739 ///
740 @safe unittest
741 {
742 JSONValue j = JSONValue( ["Perl", "C"] );
743 j[1].str = "D";
744 assert( j[1].str == "D" );
745 }
746
747 JSONValue opBinary(string op : "~", T)(T arg)
748 {
749 auto a = this.arrayNoRef;
750 static if (isArray!T)
751 {
752 return JSONValue(a ~ JSONValue(arg).arrayNoRef);
753 }
754 else static if (is(T : JSONValue))
755 {
756 return JSONValue(a ~ arg.arrayNoRef);
757 }
758 else
759 {
760 static assert(false, "argument is not an array or a JSONValue array");
761 }
762 }
763
764 void opOpAssign(string op : "~", T)(T arg)
765 {
766 auto a = this.arrayNoRef;
767 static if (isArray!T)
768 {
769 a ~= JSONValue(arg).arrayNoRef;
770 }
771 else static if (is(T : JSONValue))
772 {
773 a ~= arg.arrayNoRef;
774 }
775 else
776 {
777 static assert(false, "argument is not an array or a JSONValue array");
778 }
779 this.array = a;
780 }
781
782 /**
783 * Provides support for the `in` operator.
784 *
785 * Tests whether a key can be found in an object.
786 *
787 * Returns:
788 * When found, the `inout(JSONValue)*` that matches to the key,
789 * otherwise `null`.
790 *
791 * Throws: `JSONException` if the right hand side argument `JSONType`
792 * is not `object`.
793 */
794 inout(JSONValue)* opBinaryRight(string op : "in")(string k) inout @safe
795 {
796 return k in this.objectNoRef;
797 }
798 ///
799 @safe unittest
800 {
801 JSONValue j = [ "language": "D", "author": "walter" ];
802 string a = ("author" in j).str;
803 *("author" in j) = "Walter";
804 assert(j["author"].str == "Walter");
805 }
806
807 ///
808 bool opEquals(const JSONValue rhs) const @nogc nothrow pure @safe
809 {
810 return opEquals(rhs);
811 }
812
813 /// ditto
814 bool opEquals(ref const JSONValue rhs) const @nogc nothrow pure @trusted
815 {
816 // Default doesn't work well since store is a union. Compare only
817 // what should be in store.
818 // This is @trusted to remain nogc, nothrow, fast, and usable from @safe code.
819
820 final switch (type_tag)
821 {
822 case JSONType.integer:
823 switch (rhs.type_tag)
824 {
825 case JSONType.integer:
826 return store.integer == rhs.store.integer;
827 case JSONType.uinteger:
828 return store.integer == rhs.store.uinteger;
829 case JSONType.float_:
830 return store.integer == rhs.store.floating;
831 default:
832 return false;
833 }
834 case JSONType.uinteger:
835 switch (rhs.type_tag)
836 {
837 case JSONType.integer:
838 return store.uinteger == rhs.store.integer;
839 case JSONType.uinteger:
840 return store.uinteger == rhs.store.uinteger;
841 case JSONType.float_:
842 return store.uinteger == rhs.store.floating;
843 default:
844 return false;
845 }
846 case JSONType.float_:
847 switch (rhs.type_tag)
848 {
849 case JSONType.integer:
850 return store.floating == rhs.store.integer;
851 case JSONType.uinteger:
852 return store.floating == rhs.store.uinteger;
853 case JSONType.float_:
854 return store.floating == rhs.store.floating;
855 default:
856 return false;
857 }
858 case JSONType.string:
859 return type_tag == rhs.type_tag && store.str == rhs.store.str;
860 case JSONType.object:
861 return type_tag == rhs.type_tag && store.object == rhs.store.object;
862 case JSONType.array:
863 return type_tag == rhs.type_tag && store.array == rhs.store.array;
864 case JSONType.true_:
865 case JSONType.false_:
866 case JSONType.null_:
867 return type_tag == rhs.type_tag;
868 }
869 }
870
871 ///
872 @safe unittest
873 {
874 assert(JSONValue(0u) == JSONValue(0));
875 assert(JSONValue(0u) == JSONValue(0.0));
876 assert(JSONValue(0) == JSONValue(0.0));
877 }
878
879 /// Implements the foreach `opApply` interface for json arrays.
880 int opApply(scope int delegate(size_t index, ref JSONValue) dg) @system
881 {
882 int result;
883
884 foreach (size_t index, ref value; array)
885 {
886 result = dg(index, value);
887 if (result)
888 break;
889 }
890
891 return result;
892 }
893
894 /// Implements the foreach `opApply` interface for json objects.
895 int opApply(scope int delegate(string key, ref JSONValue) dg) @system
896 {
897 enforce!JSONException(type == JSONType.object,
898 "JSONValue is not an object");
899 int result;
900
901 foreach (string key, ref value; object)
902 {
903 result = dg(key, value);
904 if (result)
905 break;
906 }
907
908 return result;
909 }
910
911 /***
912 * Implicitly calls `toJSON` on this JSONValue.
913 *
914 * $(I options) can be used to tweak the conversion behavior.
915 */
916 string toString(in JSONOptions options = JSONOptions.none) const @safe
917 {
918 return toJSON(this, false, options);
919 }
920
921 ///
922 void toString(Out)(Out sink, in JSONOptions options = JSONOptions.none) const
923 {
924 toJSON(sink, this, false, options);
925 }
926
927 /***
928 * Implicitly calls `toJSON` on this JSONValue, like `toString`, but
929 * also passes $(I true) as $(I pretty) argument.
930 *
931 * $(I options) can be used to tweak the conversion behavior
932 */
933 string toPrettyString(in JSONOptions options = JSONOptions.none) const @safe
934 {
935 return toJSON(this, true, options);
936 }
937
938 ///
939 void toPrettyString(Out)(Out sink, in JSONOptions options = JSONOptions.none) const
940 {
941 toJSON(sink, this, true, options);
942 }
943 }
944
945 // https://issues.dlang.org/show_bug.cgi?id=20874
946 @system unittest
947 {
948 static struct MyCustomType
949 {
950 public string toString () const @system { return null; }
951 alias toString this;
952 }
953
954 static struct B
955 {
956 public JSONValue asJSON() const @system { return JSONValue.init; }
957 alias asJSON this;
958 }
959
960 if (false) // Just checking attributes
961 {
962 JSONValue json;
963 MyCustomType ilovedlang;
964 json = ilovedlang;
965 json["foo"] = ilovedlang;
966 auto s = ilovedlang in json;
967
968 B b;
969 json ~= b;
970 json ~ b;
971 }
972 }
973
974 /**
975 Parses a serialized string and returns a tree of JSON values.
976 Throws: $(LREF JSONException) if string does not follow the JSON grammar or the depth exceeds the max depth,
977 $(LREF ConvException) if a number in the input cannot be represented by a native D type.
978 Params:
979 json = json-formatted string to parse
980 maxDepth = maximum depth of nesting allowed, -1 disables depth checking
981 options = enable decoding string representations of NaN/Inf as float values
982 */
983 JSONValue parseJSON(T)(T json, int maxDepth = -1, JSONOptions options = JSONOptions.none)
984 if (isSomeFiniteCharInputRange!T)
985 {
986 import std.ascii : isDigit, isHexDigit, toUpper, toLower;
987 import std.typecons : Nullable, Yes;
988 JSONValue root;
989 root.type_tag = JSONType.null_;
990
991 // Avoid UTF decoding when possible, as it is unnecessary when
992 // processing JSON.
993 static if (is(T : const(char)[]))
994 alias Char = char;
995 else
996 alias Char = Unqual!(ElementType!T);
997
998 int depth = -1;
999 Nullable!Char next;
1000 int line = 1, pos = 0;
1001 immutable bool strict = (options & JSONOptions.strictParsing) != 0;
1002
1003 void error(string msg)
1004 {
1005 throw new JSONException(msg, line, pos);
1006 }
1007
1008 if (json.empty)
1009 {
1010 if (strict)
1011 {
1012 error("Empty JSON body");
1013 }
1014 return root;
1015 }
1016
1017 bool isWhite(dchar c)
1018 {
1019 if (strict)
1020 {
1021 // RFC 7159 has a stricter definition of whitespace than general ASCII.
1022 return c == ' ' || c == '\t' || c == '\n' || c == '\r';
1023 }
1024 import std.ascii : isWhite;
1025 // Accept ASCII NUL as whitespace in non-strict mode.
1026 return c == 0 || isWhite(c);
1027 }
1028
1029 Char popChar()
1030 {
1031 if (json.empty) error("Unexpected end of data.");
1032 static if (is(T : const(char)[]))
1033 {
1034 Char c = json[0];
1035 json = json[1..$];
1036 }
1037 else
1038 {
1039 Char c = json.front;
1040 json.popFront();
1041 }
1042
1043 if (c == '\n')
1044 {
1045 line++;
1046 pos = 0;
1047 }
1048 else
1049 {
1050 pos++;
1051 }
1052
1053 return c;
1054 }
1055
1056 Char peekChar()
1057 {
1058 if (next.isNull)
1059 {
1060 if (json.empty) return '\0';
1061 next = popChar();
1062 }
1063 return next.get;
1064 }
1065
1066 Nullable!Char peekCharNullable()
1067 {
1068 if (next.isNull && !json.empty)
1069 {
1070 next = popChar();
1071 }
1072 return next;
1073 }
1074
1075 void skipWhitespace()
1076 {
1077 while (true)
1078 {
1079 auto c = peekCharNullable();
1080 if (c.isNull ||
1081 !isWhite(c.get))
1082 {
1083 return;
1084 }
1085 next.nullify();
1086 }
1087 }
1088
1089 Char getChar(bool SkipWhitespace = false)()
1090 {
1091 static if (SkipWhitespace) skipWhitespace();
1092
1093 Char c;
1094 if (!next.isNull)
1095 {
1096 c = next.get;
1097 next.nullify();
1098 }
1099 else
1100 c = popChar();
1101
1102 return c;
1103 }
1104
1105 void checkChar(bool SkipWhitespace = true)(char c, bool caseSensitive = true)
1106 {
1107 static if (SkipWhitespace) skipWhitespace();
1108 auto c2 = getChar();
1109 if (!caseSensitive) c2 = toLower(c2);
1110
1111 if (c2 != c) error(text("Found '", c2, "' when expecting '", c, "'."));
1112 }
1113
1114 bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c)
1115 {
1116 static if (SkipWhitespace) skipWhitespace();
1117 auto c2 = peekChar();
1118 static if (!CaseSensitive) c2 = toLower(c2);
1119
1120 if (c2 != c) return false;
1121
1122 getChar();
1123 return true;
1124 }
1125
1126 wchar parseWChar()
1127 {
1128 wchar val = 0;
1129 foreach_reverse (i; 0 .. 4)
1130 {
1131 auto hex = toUpper(getChar());
1132 if (!isHexDigit(hex)) error("Expecting hex character");
1133 val += (isDigit(hex) ? hex - '0' : hex - ('A' - 10)) << (4 * i);
1134 }
1135 return val;
1136 }
1137
1138 string parseString()
1139 {
1140 import std.uni : isSurrogateHi, isSurrogateLo;
1141 import std.utf : encode, decode;
1142
1143 auto str = appender!string();
1144
1145 Next:
1146 switch (peekChar())
1147 {
1148 case '"':
1149 getChar();
1150 break;
1151
1152 case '\\':
1153 getChar();
1154 auto c = getChar();
1155 switch (c)
1156 {
1157 case '"': str.put('"'); break;
1158 case '\\': str.put('\\'); break;
1159 case '/': str.put('/'); break;
1160 case 'b': str.put('\b'); break;
1161 case 'f': str.put('\f'); break;
1162 case 'n': str.put('\n'); break;
1163 case 'r': str.put('\r'); break;
1164 case 't': str.put('\t'); break;
1165 case 'u':
1166 wchar wc = parseWChar();
1167 dchar val;
1168 // Non-BMP characters are escaped as a pair of
1169 // UTF-16 surrogate characters (see RFC 4627).
1170 if (isSurrogateHi(wc))
1171 {
1172 wchar[2] pair;
1173 pair[0] = wc;
1174 if (getChar() != '\\') error("Expected escaped low surrogate after escaped high surrogate");
1175 if (getChar() != 'u') error("Expected escaped low surrogate after escaped high surrogate");
1176 pair[1] = parseWChar();
1177 size_t index = 0;
1178 val = decode(pair[], index);
1179 if (index != 2) error("Invalid escaped surrogate pair");
1180 }
1181 else
1182 if (isSurrogateLo(wc))
1183 error(text("Unexpected low surrogate"));
1184 else
1185 val = wc;
1186
1187 char[4] buf;
1188 immutable len = encode!(Yes.useReplacementDchar)(buf, val);
1189 str.put(buf[0 .. len]);
1190 break;
1191
1192 default:
1193 error(text("Invalid escape sequence '\\", c, "'."));
1194 }
1195 goto Next;
1196
1197 default:
1198 // RFC 7159 states that control characters U+0000 through
1199 // U+001F must not appear unescaped in a JSON string.
1200 // Note: std.ascii.isControl can't be used for this test
1201 // because it considers ASCII DEL (0x7f) to be a control
1202 // character but RFC 7159 does not.
1203 // Accept unescaped ASCII NULs in non-strict mode.
1204 auto c = getChar();
1205 if (c < 0x20 && (strict || c != 0))
1206 error("Illegal control character.");
1207 str.put(c);
1208 goto Next;
1209 }
1210
1211 return str.data.length ? str.data : "";
1212 }
1213
1214 bool tryGetSpecialFloat(string str, out double val) {
1215 switch (str)
1216 {
1217 case JSONFloatLiteral.nan:
1218 val = double.nan;
1219 return true;
1220 case JSONFloatLiteral.inf:
1221 val = double.infinity;
1222 return true;
1223 case JSONFloatLiteral.negativeInf:
1224 val = -double.infinity;
1225 return true;
1226 default:
1227 return false;
1228 }
1229 }
1230
1231 void parseValue(ref JSONValue value)
1232 {
1233 depth++;
1234
1235 if (maxDepth != -1 && depth > maxDepth) error("Nesting too deep.");
1236
1237 auto c = getChar!true();
1238
1239 switch (c)
1240 {
1241 case '{':
1242 if (testChar('}'))
1243 {
1244 value.object = null;
1245 break;
1246 }
1247
1248 JSONValue[string] obj;
1249 do
1250 {
1251 skipWhitespace();
1252 if (!strict && peekChar() == '}')
1253 {
1254 break;
1255 }
1256 checkChar('"');
1257 string name = parseString();
1258 checkChar(':');
1259 JSONValue member;
1260 parseValue(member);
1261 obj[name] = member;
1262 }
1263 while (testChar(','));
1264 value.object = obj;
1265
1266 checkChar('}');
1267 break;
1268
1269 case '[':
1270 if (testChar(']'))
1271 {
1272 value.type_tag = JSONType.array;
1273 break;
1274 }
1275
1276 JSONValue[] arr;
1277 do
1278 {
1279 skipWhitespace();
1280 if (!strict && peekChar() == ']')
1281 {
1282 break;
1283 }
1284 JSONValue element;
1285 parseValue(element);
1286 arr ~= element;
1287 }
1288 while (testChar(','));
1289
1290 checkChar(']');
1291 value.array = arr;
1292 break;
1293
1294 case '"':
1295 auto str = parseString();
1296
1297 // if special float parsing is enabled, check if string represents NaN/Inf
1298 if ((options & JSONOptions.specialFloatLiterals) &&
1299 tryGetSpecialFloat(str, value.store.floating))
1300 {
1301 // found a special float, its value was placed in value.store.floating
1302 value.type_tag = JSONType.float_;
1303 break;
1304 }
1305
1306 value.assign(str);
1307 break;
1308
1309 case '0': .. case '9':
1310 case '-':
1311 auto number = appender!string();
1312 bool isFloat, isNegative;
1313
1314 void readInteger()
1315 {
1316 if (!isDigit(c)) error("Digit expected");
1317
1318 Next: number.put(c);
1319
1320 if (isDigit(peekChar()))
1321 {
1322 c = getChar();
1323 goto Next;
1324 }
1325 }
1326
1327 if (c == '-')
1328 {
1329 number.put('-');
1330 c = getChar();
1331 isNegative = true;
1332 }
1333
1334 if (strict && c == '0')
1335 {
1336 number.put('0');
1337 if (isDigit(peekChar()))
1338 {
1339 error("Additional digits not allowed after initial zero digit");
1340 }
1341 }
1342 else
1343 {
1344 readInteger();
1345 }
1346
1347 if (testChar('.'))
1348 {
1349 isFloat = true;
1350 number.put('.');
1351 c = getChar();
1352 readInteger();
1353 }
1354 if (testChar!(false, false)('e'))
1355 {
1356 isFloat = true;
1357 number.put('e');
1358 if (testChar('+')) number.put('+');
1359 else if (testChar('-')) number.put('-');
1360 c = getChar();
1361 readInteger();
1362 }
1363
1364 string data = number.data;
1365 if (isFloat)
1366 {
1367 value.type_tag = JSONType.float_;
1368 value.store.floating = parse!double(data);
1369 }
1370 else
1371 {
1372 if (isNegative)
1373 {
1374 value.store.integer = parse!long(data);
1375 value.type_tag = JSONType.integer;
1376 }
1377 else
1378 {
1379 // only set the correct union member to not confuse CTFE
1380 ulong u = parse!ulong(data);
1381 if (u & (1UL << 63))
1382 {
1383 value.store.uinteger = u;
1384 value.type_tag = JSONType.uinteger;
1385 }
1386 else
1387 {
1388 value.store.integer = u;
1389 value.type_tag = JSONType.integer;
1390 }
1391 }
1392 }
1393 break;
1394
1395 case 'T':
1396 if (strict) goto default;
1397 goto case;
1398 case 't':
1399 value.type_tag = JSONType.true_;
1400 checkChar!false('r', strict);
1401 checkChar!false('u', strict);
1402 checkChar!false('e', strict);
1403 break;
1404
1405 case 'F':
1406 if (strict) goto default;
1407 goto case;
1408 case 'f':
1409 value.type_tag = JSONType.false_;
1410 checkChar!false('a', strict);
1411 checkChar!false('l', strict);
1412 checkChar!false('s', strict);
1413 checkChar!false('e', strict);
1414 break;
1415
1416 case 'N':
1417 if (strict) goto default;
1418 goto case;
1419 case 'n':
1420 value.type_tag = JSONType.null_;
1421 checkChar!false('u', strict);
1422 checkChar!false('l', strict);
1423 checkChar!false('l', strict);
1424 break;
1425
1426 default:
1427 error(text("Unexpected character '", c, "'."));
1428 }
1429
1430 depth--;
1431 }
1432
1433 parseValue(root);
1434 if (strict)
1435 {
1436 skipWhitespace();
1437 if (!peekCharNullable().isNull) error("Trailing non-whitespace characters");
1438 }
1439 return root;
1440 }
1441
1442 @safe unittest
1443 {
1444 enum issue15742objectOfObject = `{ "key1": { "key2": 1 }}`;
1445 static assert(parseJSON(issue15742objectOfObject).type == JSONType.object);
1446
1447 enum issue15742arrayOfArray = `[[1]]`;
1448 static assert(parseJSON(issue15742arrayOfArray).type == JSONType.array);
1449 }
1450
1451 @safe unittest
1452 {
1453 // Ensure we can parse and use JSON from @safe code
1454 auto a = `{ "key1": { "key2": 1 }}`.parseJSON;
1455 assert(a["key1"]["key2"].integer == 1);
1456 assert(a.toString == `{"key1":{"key2":1}}`);
1457 }
1458
1459 @system unittest
1460 {
1461 // Ensure we can parse JSON from a @system range.
1462 struct Range
1463 {
1464 string s;
1465 size_t index;
1466 @system
1467 {
1468 bool empty() { return index >= s.length; }
1469 void popFront() { index++; }
1470 char front() { return s[index]; }
1471 }
1472 }
1473 auto s = Range(`{ "key1": { "key2": 1 }}`);
1474 auto json = parseJSON(s);
1475 assert(json["key1"]["key2"].integer == 1);
1476 }
1477
1478 // https://issues.dlang.org/show_bug.cgi?id=20527
1479 @safe unittest
1480 {
1481 static assert(parseJSON(`{"a" : 2}`)["a"].integer == 2);
1482 }
1483
1484 /**
1485 Parses a serialized string and returns a tree of JSON values.
1486 Throws: $(LREF JSONException) if the depth exceeds the max depth.
1487 Params:
1488 json = json-formatted string to parse
1489 options = enable decoding string representations of NaN/Inf as float values
1490 */
1491 JSONValue parseJSON(T)(T json, JSONOptions options)
1492 if (isSomeFiniteCharInputRange!T)
1493 {
1494 return parseJSON!T(json, -1, options);
1495 }
1496
1497 /**
1498 Takes a tree of JSON values and returns the serialized string.
1499
1500 Any Object types will be serialized in a key-sorted order.
1501
1502 If `pretty` is false no whitespaces are generated.
1503 If `pretty` is true serialized string is formatted to be human-readable.
1504 Set the $(LREF JSONOptions.specialFloatLiterals) flag is set in `options` to encode NaN/Infinity as strings.
1505 */
1506 string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions options = JSONOptions.none) @safe
1507 {
1508 auto json = appender!string();
1509 toJSON(json, root, pretty, options);
1510 return json.data;
1511 }
1512
1513 ///
1514 void toJSON(Out)(
1515 auto ref Out json,
1516 const ref JSONValue root,
1517 in bool pretty = false,
1518 in JSONOptions options = JSONOptions.none)
1519 if (isOutputRange!(Out,char))
1520 {
1521 void toStringImpl(Char)(string str)
1522 {
1523 json.put('"');
1524
1525 foreach (Char c; str)
1526 {
1527 switch (c)
1528 {
1529 case '"': json.put("\\\""); break;
1530 case '\\': json.put("\\\\"); break;
1531
1532 case '/':
1533 if (!(options & JSONOptions.doNotEscapeSlashes))
1534 json.put('\\');
1535 json.put('/');
1536 break;
1537
1538 case '\b': json.put("\\b"); break;
1539 case '\f': json.put("\\f"); break;
1540 case '\n': json.put("\\n"); break;
1541 case '\r': json.put("\\r"); break;
1542 case '\t': json.put("\\t"); break;
1543 default:
1544 {
1545 import std.ascii : isControl;
1546 import std.utf : encode;
1547
1548 // Make sure we do UTF decoding iff we want to
1549 // escape Unicode characters.
1550 assert(((options & JSONOptions.escapeNonAsciiChars) != 0)
1551 == is(Char == dchar), "JSONOptions.escapeNonAsciiChars needs dchar strings");
1552
1553 with (JSONOptions) if (isControl(c) ||
1554 ((options & escapeNonAsciiChars) >= escapeNonAsciiChars && c >= 0x80))
1555 {
1556 // Ensure non-BMP characters are encoded as a pair
1557 // of UTF-16 surrogate characters, as per RFC 4627.
1558 wchar[2] wchars; // 1 or 2 UTF-16 code units
1559 size_t wNum = encode(wchars, c); // number of UTF-16 code units
1560 foreach (wc; wchars[0 .. wNum])
1561 {
1562 json.put("\\u");
1563 foreach_reverse (i; 0 .. 4)
1564 {
1565 char ch = (wc >>> (4 * i)) & 0x0f;
1566 ch += ch < 10 ? '0' : 'A' - 10;
1567 json.put(ch);
1568 }
1569 }
1570 }
1571 else
1572 {
1573 json.put(c);
1574 }
1575 }
1576 }
1577 }
1578
1579 json.put('"');
1580 }
1581
1582 void toString(string str)
1583 {
1584 // Avoid UTF decoding when possible, as it is unnecessary when
1585 // processing JSON.
1586 if (options & JSONOptions.escapeNonAsciiChars)
1587 toStringImpl!dchar(str);
1588 else
1589 toStringImpl!char(str);
1590 }
1591
1592 /* make the function infer @system when json.put() is @system
1593 */
1594 if (0)
1595 json.put(' ');
1596
1597 /* Mark as @trusted because json.put() may be @system. This has difficulty
1598 * inferring @safe because it is recursive.
1599 */
1600 void toValueImpl(ref const JSONValue value, ulong indentLevel) @trusted
1601 {
1602 void putTabs(ulong additionalIndent = 0)
1603 {
1604 if (pretty)
1605 foreach (i; 0 .. indentLevel + additionalIndent)
1606 json.put(" ");
1607 }
1608 void putEOL()
1609 {
1610 if (pretty)
1611 json.put('\n');
1612 }
1613 void putCharAndEOL(char ch)
1614 {
1615 json.put(ch);
1616 putEOL();
1617 }
1618
1619 final switch (value.type)
1620 {
1621 case JSONType.object:
1622 auto obj = value.objectNoRef;
1623 if (!obj.length)
1624 {
1625 json.put("{}");
1626 }
1627 else
1628 {
1629 putCharAndEOL('{');
1630 bool first = true;
1631
1632 void emit(R)(R names)
1633 {
1634 foreach (name; names)
1635 {
1636 auto member = obj[name];
1637 if (!first)
1638 putCharAndEOL(',');
1639 first = false;
1640 putTabs(1);
1641 toString(name);
1642 json.put(':');
1643 if (pretty)
1644 json.put(' ');
1645 toValueImpl(member, indentLevel + 1);
1646 }
1647 }
1648
1649 import std.algorithm.sorting : sort;
1650 // https://issues.dlang.org/show_bug.cgi?id=14439
1651 // auto names = obj.keys; // aa.keys can't be called in @safe code
1652 auto names = new string[obj.length];
1653 size_t i = 0;
1654 foreach (k, v; obj)
1655 {
1656 names[i] = k;
1657 i++;
1658 }
1659 sort(names);
1660 emit(names);
1661
1662 putEOL();
1663 putTabs();
1664 json.put('}');
1665 }
1666 break;
1667
1668 case JSONType.array:
1669 auto arr = value.arrayNoRef;
1670 if (arr.empty)
1671 {
1672 json.put("[]");
1673 }
1674 else
1675 {
1676 putCharAndEOL('[');
1677 foreach (i, el; arr)
1678 {
1679 if (i)
1680 putCharAndEOL(',');
1681 putTabs(1);
1682 toValueImpl(el, indentLevel + 1);
1683 }
1684 putEOL();
1685 putTabs();
1686 json.put(']');
1687 }
1688 break;
1689
1690 case JSONType.string:
1691 toString(value.str);
1692 break;
1693
1694 case JSONType.integer:
1695 json.put(to!string(value.store.integer));
1696 break;
1697
1698 case JSONType.uinteger:
1699 json.put(to!string(value.store.uinteger));
1700 break;
1701
1702 case JSONType.float_:
1703 import std.math.traits : isNaN, isInfinity;
1704
1705 auto val = value.store.floating;
1706
1707 if (val.isNaN)
1708 {
1709 if (options & JSONOptions.specialFloatLiterals)
1710 {
1711 toString(JSONFloatLiteral.nan);
1712 }
1713 else
1714 {
1715 throw new JSONException(
1716 "Cannot encode NaN. Consider passing the specialFloatLiterals flag.");
1717 }
1718 }
1719 else if (val.isInfinity)
1720 {
1721 if (options & JSONOptions.specialFloatLiterals)
1722 {
1723 toString((val > 0) ? JSONFloatLiteral.inf : JSONFloatLiteral.negativeInf);
1724 }
1725 else
1726 {
1727 throw new JSONException(
1728 "Cannot encode Infinity. Consider passing the specialFloatLiterals flag.");
1729 }
1730 }
1731 else
1732 {
1733 import std.algorithm.searching : canFind;
1734 import std.format : sformat;
1735 // The correct formula for the number of decimal digits needed for lossless round
1736 // trips is actually:
1737 // ceil(log(pow(2.0, double.mant_dig - 1)) / log(10.0) + 1) == (double.dig + 2)
1738 // Anything less will round off (1 + double.epsilon)
1739 char[25] buf;
1740 auto result = buf[].sformat!"%.18g"(val);
1741 json.put(result);
1742 if (!result.canFind('e') && !result.canFind('.'))
1743 json.put(".0");
1744 }
1745 break;
1746
1747 case JSONType.true_:
1748 json.put("true");
1749 break;
1750
1751 case JSONType.false_:
1752 json.put("false");
1753 break;
1754
1755 case JSONType.null_:
1756 json.put("null");
1757 break;
1758 }
1759 }
1760
1761 toValueImpl(root, 0);
1762 }
1763
1764 // https://issues.dlang.org/show_bug.cgi?id=12897
1765 @safe unittest
1766 {
1767 JSONValue jv0 = JSONValue("test测试");
1768 assert(toJSON(jv0, false, JSONOptions.escapeNonAsciiChars) == `"test\u6D4B\u8BD5"`);
1769 JSONValue jv00 = JSONValue("test\u6D4B\u8BD5");
1770 assert(toJSON(jv00, false, JSONOptions.none) == `"test测试"`);
1771 assert(toJSON(jv0, false, JSONOptions.none) == `"test测试"`);
1772 JSONValue jv1 = JSONValue("été");
1773 assert(toJSON(jv1, false, JSONOptions.escapeNonAsciiChars) == `"\u00E9t\u00E9"`);
1774 JSONValue jv11 = JSONValue("\u00E9t\u00E9");
1775 assert(toJSON(jv11, false, JSONOptions.none) == `"été"`);
1776 assert(toJSON(jv1, false, JSONOptions.none) == `"été"`);
1777 }
1778
1779 // https://issues.dlang.org/show_bug.cgi?id=20511
1780 @system unittest
1781 {
1782 import std.format.write : formattedWrite;
1783 import std.range : nullSink, outputRangeObject;
1784
1785 outputRangeObject!(const(char)[])(nullSink)
1786 .formattedWrite!"%s"(JSONValue.init);
1787 }
1788
1789 // Issue 16432 - JSON incorrectly parses to string
1790 @safe unittest
1791 {
1792 // Floating points numbers are rounded to the nearest integer and thus get
1793 // incorrectly parsed
1794
1795 import std.math.operations : isClose;
1796
1797 string s = "{\"rating\": 3.0 }";
1798 JSONValue j = parseJSON(s);
1799 assert(j["rating"].type == JSONType.float_);
1800 j = j.toString.parseJSON;
1801 assert(j["rating"].type == JSONType.float_);
1802 assert(isClose(j["rating"].floating, 3.0));
1803
1804 s = "{\"rating\": -3.0 }";
1805 j = parseJSON(s);
1806 assert(j["rating"].type == JSONType.float_);
1807 j = j.toString.parseJSON;
1808 assert(j["rating"].type == JSONType.float_);
1809 assert(isClose(j["rating"].floating, -3.0));
1810
1811 // https://issues.dlang.org/show_bug.cgi?id=13660
1812 auto jv1 = JSONValue(4.0);
1813 auto textual = jv1.toString();
1814 auto jv2 = parseJSON(textual);
1815 assert(jv1.type == JSONType.float_);
1816 assert(textual == "4.0");
1817 assert(jv2.type == JSONType.float_);
1818 }
1819
1820 @safe unittest
1821 {
1822 // Adapted from https://github.com/dlang/phobos/pull/5005
1823 // Result from toString is not checked here, because this
1824 // might differ (%e-like or %f-like output) depending
1825 // on OS and compiler optimization.
1826 import std.math.operations : isClose;
1827
1828 // test positive extreme values
1829 JSONValue j;
1830 j["rating"] = 1e18 - 65;
1831 assert(isClose(j.toString.parseJSON["rating"].floating, 1e18 - 65));
1832
1833 j["rating"] = 1e18 - 64;
1834 assert(isClose(j.toString.parseJSON["rating"].floating, 1e18 - 64));
1835
1836 // negative extreme values
1837 j["rating"] = -1e18 + 65;
1838 assert(isClose(j.toString.parseJSON["rating"].floating, -1e18 + 65));
1839
1840 j["rating"] = -1e18 + 64;
1841 assert(isClose(j.toString.parseJSON["rating"].floating, -1e18 + 64));
1842 }
1843
1844 /**
1845 Exception thrown on JSON errors
1846 */
1847 class JSONException : Exception
1848 {
1849 this(string msg, int line = 0, int pos = 0) pure nothrow @safe
1850 {
1851 if (line)
1852 super(text(msg, " (Line ", line, ":", pos, ")"));
1853 else
1854 super(msg);
1855 }
1856
1857 this(string msg, string file, size_t line) pure nothrow @safe
1858 {
1859 super(msg, file, line);
1860 }
1861 }
1862
1863
1864 @system unittest
1865 {
1866 import std.exception;
1867 JSONValue jv = "123";
1868 assert(jv.type == JSONType.string);
1869 assertNotThrown(jv.str);
1870 assertThrown!JSONException(jv.integer);
1871 assertThrown!JSONException(jv.uinteger);
1872 assertThrown!JSONException(jv.floating);
1873 assertThrown!JSONException(jv.object);
1874 assertThrown!JSONException(jv.array);
1875 assertThrown!JSONException(jv["aa"]);
1876 assertThrown!JSONException(jv[2]);
1877
1878 jv = -3;
1879 assert(jv.type == JSONType.integer);
1880 assertNotThrown(jv.integer);
1881
1882 jv = cast(uint) 3;
1883 assert(jv.type == JSONType.uinteger);
1884 assertNotThrown(jv.uinteger);
1885
1886 jv = 3.0;
1887 assert(jv.type == JSONType.float_);
1888 assertNotThrown(jv.floating);
1889
1890 jv = ["key" : "value"];
1891 assert(jv.type == JSONType.object);
1892 assertNotThrown(jv.object);
1893 assertNotThrown(jv["key"]);
1894 assert("key" in jv);
1895 assert("notAnElement" !in jv);
1896 assertThrown!JSONException(jv["notAnElement"]);
1897 const cjv = jv;
1898 assert("key" in cjv);
1899 assertThrown!JSONException(cjv["notAnElement"]);
1900
1901 foreach (string key, value; jv)
1902 {
1903 static assert(is(typeof(value) == JSONValue));
1904 assert(key == "key");
1905 assert(value.type == JSONType.string);
1906 assertNotThrown(value.str);
1907 assert(value.str == "value");
1908 }
1909
1910 jv = [3, 4, 5];
1911 assert(jv.type == JSONType.array);
1912 assertNotThrown(jv.array);
1913 assertNotThrown(jv[2]);
1914 foreach (size_t index, value; jv)
1915 {
1916 static assert(is(typeof(value) == JSONValue));
1917 assert(value.type == JSONType.integer);
1918 assertNotThrown(value.integer);
1919 assert(index == (value.integer-3));
1920 }
1921
1922 jv = null;
1923 assert(jv.type == JSONType.null_);
1924 assert(jv.isNull);
1925 jv = "foo";
1926 assert(!jv.isNull);
1927
1928 jv = JSONValue("value");
1929 assert(jv.type == JSONType.string);
1930 assert(jv.str == "value");
1931
1932 JSONValue jv2 = JSONValue("value");
1933 assert(jv2.type == JSONType.string);
1934 assert(jv2.str == "value");
1935
1936 JSONValue jv3 = JSONValue("\u001c");
1937 assert(jv3.type == JSONType.string);
1938 assert(jv3.str == "\u001C");
1939 }
1940
1941 // https://issues.dlang.org/show_bug.cgi?id=11504
1942 @system unittest
1943 {
1944 JSONValue jv = 1;
1945 assert(jv.type == JSONType.integer);
1946
1947 jv.str = "123";
1948 assert(jv.type == JSONType.string);
1949 assert(jv.str == "123");
1950
1951 jv.integer = 1;
1952 assert(jv.type == JSONType.integer);
1953 assert(jv.integer == 1);
1954
1955 jv.uinteger = 2u;
1956 assert(jv.type == JSONType.uinteger);
1957 assert(jv.uinteger == 2u);
1958
1959 jv.floating = 1.5;
1960 assert(jv.type == JSONType.float_);
1961 assert(jv.floating == 1.5);
1962
1963 jv.object = ["key" : JSONValue("value")];
1964 assert(jv.type == JSONType.object);
1965 assert(jv.object == ["key" : JSONValue("value")]);
1966
1967 jv.array = [JSONValue(1), JSONValue(2), JSONValue(3)];
1968 assert(jv.type == JSONType.array);
1969 assert(jv.array == [JSONValue(1), JSONValue(2), JSONValue(3)]);
1970
1971 jv = true;
1972 assert(jv.type == JSONType.true_);
1973
1974 jv = false;
1975 assert(jv.type == JSONType.false_);
1976
1977 enum E{True = true}
1978 jv = E.True;
1979 assert(jv.type == JSONType.true_);
1980 }
1981
1982 @system pure unittest
1983 {
1984 // Adding new json element via array() / object() directly
1985
1986 JSONValue jarr = JSONValue([10]);
1987 foreach (i; 0 .. 9)
1988 jarr.array ~= JSONValue(i);
1989 assert(jarr.array.length == 10);
1990
1991 JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
1992 foreach (i; 0 .. 9)
1993 jobj.object[text("key", i)] = JSONValue(text("value", i));
1994 assert(jobj.object.length == 10);
1995 }
1996
1997 @system pure unittest
1998 {
1999 // Adding new json element without array() / object() access
2000
2001 JSONValue jarr = JSONValue([10]);
2002 foreach (i; 0 .. 9)
2003 jarr ~= [JSONValue(i)];
2004 assert(jarr.array.length == 10);
2005
2006 JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
2007 foreach (i; 0 .. 9)
2008 jobj[text("key", i)] = JSONValue(text("value", i));
2009 assert(jobj.object.length == 10);
2010
2011 // No array alias
2012 auto jarr2 = jarr ~ [1,2,3];
2013 jarr2[0] = 999;
2014 assert(jarr[0] == JSONValue(10));
2015 }
2016
2017 @system unittest
2018 {
2019 // @system because JSONValue.array is @system
2020 import std.exception;
2021
2022 // An overly simple test suite, if it can parse a serializated string and
2023 // then use the resulting values tree to generate an identical
2024 // serialization, both the decoder and encoder works.
2025
2026 auto jsons = [
2027 `null`,
2028 `true`,
2029 `false`,
2030 `0`,
2031 `123`,
2032 `-4321`,
2033 `0.25`,
2034 `-0.25`,
2035 `""`,
2036 `"hello\nworld"`,
2037 `"\"\\\/\b\f\n\r\t"`,
2038 `[]`,
2039 `[12,"foo",true,false]`,
2040 `{}`,
2041 `{"a":1,"b":null}`,
2042 `{"goodbye":[true,"or",false,["test",42,{"nested":{"a":23.5,"b":0.140625}}]],`
2043 ~`"hello":{"array":[12,null,{}],"json":"is great"}}`,
2044 ];
2045
2046 enum dbl1_844 = `1.8446744073709568`;
2047 version (MinGW)
2048 jsons ~= dbl1_844 ~ `e+019`;
2049 else
2050 jsons ~= dbl1_844 ~ `e+19`;
2051
2052 JSONValue val;
2053 string result;
2054 foreach (json; jsons)
2055 {
2056 try
2057 {
2058 val = parseJSON(json);
2059 enum pretty = false;
2060 result = toJSON(val, pretty);
2061 assert(result == json, text(result, " should be ", json));
2062 }
2063 catch (JSONException e)
2064 {
2065 import std.stdio : writefln;
2066 writefln(text(json, "\n", e.toString()));
2067 }
2068 }
2069
2070 // Should be able to correctly interpret unicode entities
2071 val = parseJSON(`"\u003C\u003E"`);
2072 assert(toJSON(val) == "\"\&lt;\&gt;\"");
2073 assert(val.to!string() == "\"\&lt;\&gt;\"");
2074 val = parseJSON(`"\u0391\u0392\u0393"`);
2075 assert(toJSON(val) == "\"\&Alpha;\&Beta;\&Gamma;\"");
2076 assert(val.to!string() == "\"\&Alpha;\&Beta;\&Gamma;\"");
2077 val = parseJSON(`"\u2660\u2666"`);
2078 assert(toJSON(val) == "\"\&spades;\&diams;\"");
2079 assert(val.to!string() == "\"\&spades;\&diams;\"");
2080
2081 //0x7F is a control character (see Unicode spec)
2082 val = parseJSON(`"\u007F"`);
2083 assert(toJSON(val) == "\"\\u007F\"");
2084 assert(val.to!string() == "\"\\u007F\"");
2085
2086 with(parseJSON(`""`))
2087 assert(str == "" && str !is null);
2088 with(parseJSON(`[]`))
2089 assert(!array.length);
2090
2091 // Formatting
2092 val = parseJSON(`{"a":[null,{"x":1},{},[]]}`);
2093 assert(toJSON(val, true) == `{
2094 "a": [
2095 null,
2096 {
2097 "x": 1
2098 },
2099 {},
2100 []
2101 ]
2102 }`);
2103 }
2104
2105 @safe unittest
2106 {
2107 auto json = `"hello\nworld"`;
2108 const jv = parseJSON(json);
2109 assert(jv.toString == json);
2110 assert(jv.toPrettyString == json);
2111 }
2112
2113 @system pure unittest
2114 {
2115 // https://issues.dlang.org/show_bug.cgi?id=12969
2116
2117 JSONValue jv;
2118 jv["int"] = 123;
2119
2120 assert(jv.type == JSONType.object);
2121 assert("int" in jv);
2122 assert(jv["int"].integer == 123);
2123
2124 jv["array"] = [1, 2, 3, 4, 5];
2125
2126 assert(jv["array"].type == JSONType.array);
2127 assert(jv["array"][2].integer == 3);
2128
2129 jv["str"] = "D language";
2130 assert(jv["str"].type == JSONType.string);
2131 assert(jv["str"].str == "D language");
2132
2133 jv["bool"] = false;
2134 assert(jv["bool"].type == JSONType.false_);
2135
2136 assert(jv.object.length == 4);
2137
2138 jv = [5, 4, 3, 2, 1];
2139 assert(jv.type == JSONType.array);
2140 assert(jv[3].integer == 2);
2141 }
2142
2143 @safe unittest
2144 {
2145 auto s = q"EOF
2146 [
2147 1,
2148 2,
2149 3,
2150 potato
2151 ]
2152 EOF";
2153
2154 import std.exception;
2155
2156 auto e = collectException!JSONException(parseJSON(s));
2157 assert(e.msg == "Unexpected character 'p'. (Line 5:3)", e.msg);
2158 }
2159
2160 // handling of special float values (NaN, Inf, -Inf)
2161 @safe unittest
2162 {
2163 import std.exception : assertThrown;
2164 import std.math.traits : isNaN, isInfinity;
2165
2166 // expected representations of NaN and Inf
2167 enum {
2168 nanString = '"' ~ JSONFloatLiteral.nan ~ '"',
2169 infString = '"' ~ JSONFloatLiteral.inf ~ '"',
2170 negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"',
2171 }
2172
2173 // with the specialFloatLiterals option, encode NaN/Inf as strings
2174 assert(JSONValue(float.nan).toString(JSONOptions.specialFloatLiterals) == nanString);
2175 assert(JSONValue(double.infinity).toString(JSONOptions.specialFloatLiterals) == infString);
2176 assert(JSONValue(-real.infinity).toString(JSONOptions.specialFloatLiterals) == negativeInfString);
2177
2178 // without the specialFloatLiterals option, throw on encoding NaN/Inf
2179 assertThrown!JSONException(JSONValue(float.nan).toString);
2180 assertThrown!JSONException(JSONValue(double.infinity).toString);
2181 assertThrown!JSONException(JSONValue(-real.infinity).toString);
2182
2183 // when parsing json with specialFloatLiterals option, decode special strings as floats
2184 JSONValue jvNan = parseJSON(nanString, JSONOptions.specialFloatLiterals);
2185 JSONValue jvInf = parseJSON(infString, JSONOptions.specialFloatLiterals);
2186 JSONValue jvNegInf = parseJSON(negativeInfString, JSONOptions.specialFloatLiterals);
2187
2188 assert(jvNan.floating.isNaN);
2189 assert(jvInf.floating.isInfinity && jvInf.floating > 0);
2190 assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0);
2191
2192 // when parsing json without the specialFloatLiterals option, decode special strings as strings
2193 jvNan = parseJSON(nanString);
2194 jvInf = parseJSON(infString);
2195 jvNegInf = parseJSON(negativeInfString);
2196
2197 assert(jvNan.str == JSONFloatLiteral.nan);
2198 assert(jvInf.str == JSONFloatLiteral.inf);
2199 assert(jvNegInf.str == JSONFloatLiteral.negativeInf);
2200 }
2201
2202 pure nothrow @safe @nogc unittest
2203 {
2204 JSONValue testVal;
2205 testVal = "test";
2206 testVal = 10;
2207 testVal = 10u;
2208 testVal = 1.0;
2209 testVal = (JSONValue[string]).init;
2210 testVal = JSONValue[].init;
2211 testVal = null;
2212 assert(testVal.isNull);
2213 }
2214
2215 // https://issues.dlang.org/show_bug.cgi?id=15884
2216 pure nothrow @safe unittest
2217 {
2218 import std.typecons;
2219 void Test(C)() {
2220 C[] a = ['x'];
2221 JSONValue testVal = a;
2222 assert(testVal.type == JSONType.string);
2223 testVal = a.idup;
2224 assert(testVal.type == JSONType.string);
2225 }
2226 Test!char();
2227 Test!wchar();
2228 Test!dchar();
2229 }
2230
2231 // https://issues.dlang.org/show_bug.cgi?id=15885
2232 @safe unittest
2233 {
2234 enum bool realInDoublePrecision = real.mant_dig == double.mant_dig;
2235
2236 static bool test(const double num0)
2237 {
2238 import std.math.operations : feqrel;
2239 const json0 = JSONValue(num0);
2240 const num1 = to!double(toJSON(json0));
2241 static if (realInDoublePrecision)
2242 return feqrel(num1, num0) >= (double.mant_dig - 1);
2243 else
2244 return num1 == num0;
2245 }
2246
2247 assert(test( 0.23));
2248 assert(test(-0.23));
2249 assert(test(1.223e+24));
2250 assert(test(23.4));
2251 assert(test(0.0012));
2252 assert(test(30738.22));
2253
2254 assert(test(1 + double.epsilon));
2255 assert(test(double.min_normal));
2256 static if (realInDoublePrecision)
2257 assert(test(-double.max / 2));
2258 else
2259 assert(test(-double.max));
2260
2261 const minSub = double.min_normal * double.epsilon;
2262 assert(test(minSub));
2263 assert(test(3*minSub));
2264 }
2265
2266 // https://issues.dlang.org/show_bug.cgi?id=17555
2267 @safe unittest
2268 {
2269 import std.exception : assertThrown;
2270
2271 assertThrown!JSONException(parseJSON("\"a\nb\""));
2272 }
2273
2274 // https://issues.dlang.org/show_bug.cgi?id=17556
2275 @safe unittest
2276 {
2277 auto v = JSONValue("\U0001D11E");
2278 auto j = toJSON(v, false, JSONOptions.escapeNonAsciiChars);
2279 assert(j == `"\uD834\uDD1E"`);
2280 }
2281
2282 // https://issues.dlang.org/show_bug.cgi?id=5904
2283 @safe unittest
2284 {
2285 string s = `"\uD834\uDD1E"`;
2286 auto j = parseJSON(s);
2287 assert(j.str == "\U0001D11E");
2288 }
2289
2290 // https://issues.dlang.org/show_bug.cgi?id=17557
2291 @safe unittest
2292 {
2293 assert(parseJSON("\"\xFF\"").str == "\xFF");
2294 assert(parseJSON("\"\U0001D11E\"").str == "\U0001D11E");
2295 }
2296
2297 // https://issues.dlang.org/show_bug.cgi?id=17553
2298 @safe unittest
2299 {
2300 auto v = JSONValue("\xFF");
2301 assert(toJSON(v) == "\"\xFF\"");
2302 }
2303
2304 @safe unittest
2305 {
2306 import std.utf;
2307 assert(parseJSON("\"\xFF\"".byChar).str == "\xFF");
2308 assert(parseJSON("\"\U0001D11E\"".byChar).str == "\U0001D11E");
2309 }
2310
2311 // JSONOptions.doNotEscapeSlashes (https://issues.dlang.org/show_bug.cgi?id=17587)
2312 @safe unittest
2313 {
2314 assert(parseJSON(`"/"`).toString == `"\/"`);
2315 assert(parseJSON(`"\/"`).toString == `"\/"`);
2316 assert(parseJSON(`"/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
2317 assert(parseJSON(`"\/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
2318 }
2319
2320 // JSONOptions.strictParsing (https://issues.dlang.org/show_bug.cgi?id=16639)
2321 @safe unittest
2322 {
2323 import std.exception : assertThrown;
2324
2325 // Unescaped ASCII NULs
2326 assert(parseJSON("[\0]").type == JSONType.array);
2327 assertThrown!JSONException(parseJSON("[\0]", JSONOptions.strictParsing));
2328 assert(parseJSON("\"\0\"").str == "\0");
2329 assertThrown!JSONException(parseJSON("\"\0\"", JSONOptions.strictParsing));
2330
2331 // Unescaped ASCII DEL (0x7f) in strings
2332 assert(parseJSON("\"\x7f\"").str == "\x7f");
2333 assert(parseJSON("\"\x7f\"", JSONOptions.strictParsing).str == "\x7f");
2334
2335 // "true", "false", "null" case sensitivity
2336 assert(parseJSON("true").type == JSONType.true_);
2337 assert(parseJSON("true", JSONOptions.strictParsing).type == JSONType.true_);
2338 assert(parseJSON("True").type == JSONType.true_);
2339 assertThrown!JSONException(parseJSON("True", JSONOptions.strictParsing));
2340 assert(parseJSON("tRUE").type == JSONType.true_);
2341 assertThrown!JSONException(parseJSON("tRUE", JSONOptions.strictParsing));
2342
2343 assert(parseJSON("false").type == JSONType.false_);
2344 assert(parseJSON("false", JSONOptions.strictParsing).type == JSONType.false_);
2345 assert(parseJSON("False").type == JSONType.false_);
2346 assertThrown!JSONException(parseJSON("False", JSONOptions.strictParsing));
2347 assert(parseJSON("fALSE").type == JSONType.false_);
2348 assertThrown!JSONException(parseJSON("fALSE", JSONOptions.strictParsing));
2349
2350 assert(parseJSON("null").type == JSONType.null_);
2351 assert(parseJSON("null", JSONOptions.strictParsing).type == JSONType.null_);
2352 assert(parseJSON("Null").type == JSONType.null_);
2353 assertThrown!JSONException(parseJSON("Null", JSONOptions.strictParsing));
2354 assert(parseJSON("nULL").type == JSONType.null_);
2355 assertThrown!JSONException(parseJSON("nULL", JSONOptions.strictParsing));
2356
2357 // Whitespace characters
2358 assert(parseJSON("[\f\v]").type == JSONType.array);
2359 assertThrown!JSONException(parseJSON("[\f\v]", JSONOptions.strictParsing));
2360 assert(parseJSON("[ \t\r\n]").type == JSONType.array);
2361 assert(parseJSON("[ \t\r\n]", JSONOptions.strictParsing).type == JSONType.array);
2362
2363 // Empty input
2364 assert(parseJSON("").type == JSONType.null_);
2365 assertThrown!JSONException(parseJSON("", JSONOptions.strictParsing));
2366
2367 // Numbers with leading '0's
2368 assert(parseJSON("01").integer == 1);
2369 assertThrown!JSONException(parseJSON("01", JSONOptions.strictParsing));
2370 assert(parseJSON("-01").integer == -1);
2371 assertThrown!JSONException(parseJSON("-01", JSONOptions.strictParsing));
2372 assert(parseJSON("0.01").floating == 0.01);
2373 assert(parseJSON("0.01", JSONOptions.strictParsing).floating == 0.01);
2374 assert(parseJSON("0e1").floating == 0);
2375 assert(parseJSON("0e1", JSONOptions.strictParsing).floating == 0);
2376
2377 // Trailing characters after JSON value
2378 assert(parseJSON(`""asdf`).str == "");
2379 assertThrown!JSONException(parseJSON(`""asdf`, JSONOptions.strictParsing));
2380 assert(parseJSON("987\0").integer == 987);
2381 assertThrown!JSONException(parseJSON("987\0", JSONOptions.strictParsing));
2382 assert(parseJSON("987\0\0").integer == 987);
2383 assertThrown!JSONException(parseJSON("987\0\0", JSONOptions.strictParsing));
2384 assert(parseJSON("[]]").type == JSONType.array);
2385 assertThrown!JSONException(parseJSON("[]]", JSONOptions.strictParsing));
2386 assert(parseJSON("123 \t\r\n").integer == 123); // Trailing whitespace is OK
2387 assert(parseJSON("123 \t\r\n", JSONOptions.strictParsing).integer == 123);
2388 }
2389
2390 @system unittest
2391 {
2392 import std.algorithm.iteration : map;
2393 import std.array : array;
2394 import std.exception : assertThrown;
2395
2396 string s = `{ "a" : [1,2,3,], }`;
2397 JSONValue j = parseJSON(s);
2398 assert(j["a"].array().map!(i => i.integer()).array == [1,2,3]);
2399
2400 assertThrown(parseJSON(s, -1, JSONOptions.strictParsing));
2401 }
2402
2403 @system unittest
2404 {
2405 import std.algorithm.iteration : map;
2406 import std.array : array;
2407 import std.exception : assertThrown;
2408
2409 string s = `{ "a" : { } , }`;
2410 JSONValue j = parseJSON(s);
2411 assert("a" in j);
2412 auto t = j["a"].object();
2413 assert(t.empty);
2414
2415 assertThrown(parseJSON(s, -1, JSONOptions.strictParsing));
2416 }
2417
2418 // https://issues.dlang.org/show_bug.cgi?id=20330
2419 @safe unittest
2420 {
2421 import std.array : appender;
2422
2423 string s = `{"a":[1,2,3]}`;
2424 JSONValue j = parseJSON(s);
2425
2426 auto app = appender!string();
2427 j.toString(app);
2428
2429 assert(app.data == s, app.data);
2430 }
2431
2432 // https://issues.dlang.org/show_bug.cgi?id=20330
2433 @safe unittest
2434 {
2435 import std.array : appender;
2436 import std.format.write : formattedWrite;
2437
2438 string s =
2439 `{
2440 "a": [
2441 1,
2442 2,
2443 3
2444 ]
2445 }`;
2446 JSONValue j = parseJSON(s);
2447
2448 auto app = appender!string();
2449 j.toPrettyString(app);
2450
2451 assert(app.data == s, app.data);
2452 }