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