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