d: Merge upstream dmd 47871363d, druntime, c52e28b7, phobos 99e9c1b77.
[official-gcc.git] / libphobos / src / std / json.d
blobc6e746a0beb643e2ba7d4e79eba67c367ef0cced
1 // Written in the D programming language.
3 /**
4 JavaScript Object Notation
6 Copyright: Copyright Jeremie Pelletier 2008 - 2009.
7 License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
8 Authors: Jeremie Pelletier, David Herberth
9 References: $(LINK http://json.org/), $(LINK http://seriot.ch/parsing_json.html)
10 Source: $(PHOBOSSRC std/json.d)
13 Copyright Jeremie Pelletier 2008 - 2009.
14 Distributed under the Boost Software License, Version 1.0.
15 (See accompanying file LICENSE_1_0.txt or copy at
16 http://www.boost.org/LICENSE_1_0.txt)
18 module std.json;
20 import std.array;
21 import std.conv;
22 import std.range;
23 import std.traits;
25 ///
26 @system unittest
28 import std.conv : to;
30 // parse a file or string of json into a usable structure
31 string s = `{ "language": "D", "rating": 3.5, "code": "42" }`;
32 JSONValue j = parseJSON(s);
33 // j and j["language"] return JSONValue,
34 // j["language"].str returns a string
35 assert(j["language"].str == "D");
36 assert(j["rating"].floating == 3.5);
38 // check a type
39 long x;
40 if (const(JSONValue)* code = "code" in j)
42 if (code.type() == JSONType.integer)
43 x = code.integer;
44 else
45 x = to!int(code.str);
48 // create a json struct
49 JSONValue jj = [ "language": "D" ];
50 // rating doesnt exist yet, so use .object to assign
51 jj.object["rating"] = JSONValue(3.5);
52 // create an array to assign to list
53 jj.object["list"] = JSONValue( ["a", "b", "c"] );
54 // list already exists, so .object optional
55 jj["list"].array ~= JSONValue("D");
57 string jjStr = `{"language":"D","list":["a","b","c","D"],"rating":3.5}`;
58 assert(jj.toString == jjStr);
61 /**
62 String literals used to represent special float values within JSON strings.
64 enum JSONFloatLiteral : string
66 nan = "NaN", /// string representation of floating-point NaN
67 inf = "Infinite", /// string representation of floating-point Infinity
68 negativeInf = "-Infinite", /// string representation of floating-point negative Infinity
71 /**
72 Flags that control how json is encoded and parsed.
74 enum JSONOptions
76 none, /// standard parsing
77 specialFloatLiterals = 0x1, /// encode NaN and Inf float values as strings
78 escapeNonAsciiChars = 0x2, /// encode non ascii characters with an unicode escape sequence
79 doNotEscapeSlashes = 0x4, /// do not escape slashes ('/')
80 strictParsing = 0x8, /// Strictly follow RFC-8259 grammar when parsing
83 /**
84 JSON type enumeration
86 enum JSONType : byte
88 /// Indicates the type of a `JSONValue`.
89 null_,
90 string, /// ditto
91 integer, /// ditto
92 uinteger, /// ditto
93 float_, /// ditto
94 array, /// ditto
95 object, /// ditto
96 true_, /// ditto
97 false_, /// ditto
98 // FIXME: Find some way to deprecate the enum members below, which does NOT
99 // create lots of spam-like deprecation warnings, which can't be fixed
100 // by the user. See discussion on this issue at
101 // https://forum.dlang.org/post/feudrhtxkaxxscwhhhff@forum.dlang.org
102 /* deprecated("Use .null_") */ NULL = null_,
103 /* deprecated("Use .string") */ STRING = string,
104 /* deprecated("Use .integer") */ INTEGER = integer,
105 /* deprecated("Use .uinteger") */ UINTEGER = uinteger,
106 /* deprecated("Use .float_") */ FLOAT = float_,
107 /* deprecated("Use .array") */ ARRAY = array,
108 /* deprecated("Use .object") */ OBJECT = object,
109 /* deprecated("Use .true_") */ TRUE = true_,
110 /* deprecated("Use .false_") */ FALSE = false_,
113 deprecated("Use JSONType and the new enum member names") alias JSON_TYPE = JSONType;
116 JSON value node
118 struct JSONValue
120 import std.exception : enforce;
122 union Store
124 string str;
125 long integer;
126 ulong uinteger;
127 double floating;
128 JSONValue[string] object;
129 JSONValue[] array;
131 private Store store;
132 private JSONType type_tag;
135 Returns the JSONType of the value stored in this structure.
137 @property JSONType type() const pure nothrow @safe @nogc
139 return type_tag;
142 @safe unittest
144 string s = "{ \"language\": \"D\" }";
145 JSONValue j = parseJSON(s);
146 assert(j.type == JSONType.object);
147 assert(j["language"].type == JSONType.string);
150 /***
151 * Value getter/setter for `JSONType.string`.
152 * Throws: `JSONException` for read access if `type` is not
153 * `JSONType.string`.
155 @property string str() const pure @trusted return scope
157 enforce!JSONException(type == JSONType.string,
158 "JSONValue is not a string");
159 return store.str;
161 /// ditto
162 @property string str(return scope string v) pure nothrow @nogc @trusted return // TODO make @safe
164 assign(v);
165 return v;
168 @safe unittest
170 JSONValue j = [ "language": "D" ];
172 // get value
173 assert(j["language"].str == "D");
175 // change existing key to new string
176 j["language"].str = "Perl";
177 assert(j["language"].str == "Perl");
180 /***
181 * Value getter/setter for `JSONType.integer`.
182 * Throws: `JSONException` for read access if `type` is not
183 * `JSONType.integer`.
185 @property long integer() const pure @safe
187 enforce!JSONException(type == JSONType.integer,
188 "JSONValue is not an integer");
189 return store.integer;
191 /// ditto
192 @property long integer(long v) pure nothrow @safe @nogc
194 assign(v);
195 return store.integer;
198 /***
199 * Value getter/setter for `JSONType.uinteger`.
200 * Throws: `JSONException` for read access if `type` is not
201 * `JSONType.uinteger`.
203 @property ulong uinteger() const pure @safe
205 enforce!JSONException(type == JSONType.uinteger,
206 "JSONValue is not an unsigned integer");
207 return store.uinteger;
209 /// ditto
210 @property ulong uinteger(ulong v) pure nothrow @safe @nogc
212 assign(v);
213 return store.uinteger;
216 /***
217 * Value getter/setter for `JSONType.float_`. Note that despite
218 * the name, this is a $(B 64)-bit `double`, not a 32-bit `float`.
219 * Throws: `JSONException` for read access if `type` is not
220 * `JSONType.float_`.
222 @property double floating() const pure @safe
224 enforce!JSONException(type == JSONType.float_,
225 "JSONValue is not a floating type");
226 return store.floating;
228 /// ditto
229 @property double floating(double v) pure nothrow @safe @nogc
231 assign(v);
232 return store.floating;
235 /***
236 * Value getter/setter for boolean stored in JSON.
237 * Throws: `JSONException` for read access if `this.type` is not
238 * `JSONType.true_` or `JSONType.false_`.
240 @property bool boolean() const pure @safe
242 if (type == JSONType.true_) return true;
243 if (type == JSONType.false_) return false;
245 throw new JSONException("JSONValue is not a boolean type");
247 /// ditto
248 @property bool boolean(bool v) pure nothrow @safe @nogc
250 assign(v);
251 return v;
254 @safe unittest
256 JSONValue j = true;
257 assert(j.boolean == true);
259 j.boolean = false;
260 assert(j.boolean == false);
262 j.integer = 12;
263 import std.exception : assertThrown;
264 assertThrown!JSONException(j.boolean);
267 /***
268 * Value getter/setter for `JSONType.object`.
269 * Throws: `JSONException` for read access if `type` is not
270 * `JSONType.object`.
271 * Note: this is @system because of the following pattern:
273 auto a = &(json.object());
274 json.uinteger = 0; // overwrite AA pointer
275 (*a)["hello"] = "world"; // segmentation fault
278 @property ref inout(JSONValue[string]) object() inout pure @system return
280 enforce!JSONException(type == JSONType.object,
281 "JSONValue is not an object");
282 return store.object;
284 /// ditto
285 @property JSONValue[string] object(return scope JSONValue[string] v) pure nothrow @nogc @trusted // TODO make @safe
287 assign(v);
288 return v;
291 /***
292 * Value getter for `JSONType.object`.
293 * Unlike `object`, this retrieves the object by value and can be used in @safe code.
295 * A caveat is that, if the returned value is null, modifications will not be visible:
296 * ---
297 * JSONValue json;
298 * json.object = null;
299 * json.objectNoRef["hello"] = JSONValue("world");
300 * assert("hello" !in json.object);
301 * ---
303 * Throws: `JSONException` for read access if `type` is not
304 * `JSONType.object`.
306 @property inout(JSONValue[string]) objectNoRef() inout pure @trusted
308 enforce!JSONException(type == JSONType.object,
309 "JSONValue is not an object");
310 return store.object;
313 /***
314 * Value getter/setter for `JSONType.array`.
315 * Throws: `JSONException` for read access if `type` is not
316 * `JSONType.array`.
317 * Note: this is @system because of the following pattern:
319 auto a = &(json.array());
320 json.uinteger = 0; // overwrite array pointer
321 (*a)[0] = "world"; // segmentation fault
324 @property ref inout(JSONValue[]) array() scope return inout pure @system
326 enforce!JSONException(type == JSONType.array,
327 "JSONValue is not an array");
328 return store.array;
330 /// ditto
331 @property JSONValue[] array(return scope JSONValue[] v) pure nothrow @nogc @trusted scope // TODO make @safe
333 assign(v);
334 return v;
337 /***
338 * Value getter for `JSONType.array`.
339 * Unlike `array`, this retrieves the array by value and can be used in @safe code.
341 * A caveat is that, if you append to the returned array, the new values aren't visible in the
342 * JSONValue:
343 * ---
344 * JSONValue json;
345 * json.array = [JSONValue("hello")];
346 * json.arrayNoRef ~= JSONValue("world");
347 * assert(json.array.length == 1);
348 * ---
350 * Throws: `JSONException` for read access if `type` is not
351 * `JSONType.array`.
353 @property inout(JSONValue[]) arrayNoRef() inout pure @trusted
355 enforce!JSONException(type == JSONType.array,
356 "JSONValue is not an array");
357 return store.array;
360 /// Test whether the type is `JSONType.null_`
361 @property bool isNull() const pure nothrow @safe @nogc
363 return type == JSONType.null_;
366 /***
367 * Generic type value getter
368 * A convenience getter that returns this `JSONValue` as the specified D type.
369 * Note: only numeric, `bool`, `string`, `JSONValue[string]` and `JSONValue[]` types are accepted
370 * Throws: `JSONException` if `T` cannot hold the contents of this `JSONValue`
371 * `ConvException` in case of integer overflow when converting to `T`
373 @property inout(T) get(T)() inout const pure @safe
375 static if (is(immutable T == immutable string))
377 return str;
379 else static if (is(immutable T == immutable bool))
381 return boolean;
383 else static if (isFloatingPoint!T)
385 switch (type)
387 case JSONType.float_:
388 return cast(T) floating;
389 case JSONType.uinteger:
390 return cast(T) uinteger;
391 case JSONType.integer:
392 return cast(T) integer;
393 default:
394 throw new JSONException("JSONValue is not a number type");
397 else static if (isIntegral!T)
399 switch (type)
401 case JSONType.uinteger:
402 return uinteger.to!T;
403 case JSONType.integer:
404 return integer.to!T;
405 default:
406 throw new JSONException("JSONValue is not a an integral type");
409 else
411 static assert(false, "Unsupported type");
414 // This specialization is needed because arrayNoRef requires inout
415 @property inout(T) get(T : JSONValue[])() inout pure @trusted /// ditto
417 return arrayNoRef;
419 /// ditto
420 @property inout(T) get(T : JSONValue[string])() inout pure @trusted
422 return object;
425 @safe unittest
427 import std.exception;
428 import std.conv;
429 string s =
431 "a": 123,
432 "b": 3.1415,
433 "c": "text",
434 "d": true,
435 "e": [1, 2, 3],
436 "f": { "a": 1 },
437 "g": -45,
438 "h": ` ~ ulong.max.to!string ~ `,
441 struct a { }
443 immutable json = parseJSON(s);
444 assert(json["a"].get!double == 123.0);
445 assert(json["a"].get!int == 123);
446 assert(json["a"].get!uint == 123);
447 assert(json["b"].get!double == 3.1415);
448 assertThrown!JSONException(json["b"].get!int);
449 assert(json["c"].get!string == "text");
450 assert(json["d"].get!bool == true);
451 assertNotThrown(json["e"].get!(JSONValue[]));
452 assertNotThrown(json["f"].get!(JSONValue[string]));
453 static assert(!__traits(compiles, json["a"].get!a));
454 assertThrown!JSONException(json["e"].get!float);
455 assertThrown!JSONException(json["d"].get!(JSONValue[string]));
456 assertThrown!JSONException(json["f"].get!(JSONValue[]));
457 assert(json["g"].get!int == -45);
458 assertThrown!ConvException(json["g"].get!uint);
459 assert(json["h"].get!ulong == ulong.max);
460 assertThrown!ConvException(json["h"].get!uint);
461 assertNotThrown(json["h"].get!float);
464 private void assign(T)(T arg)
466 static if (is(T : typeof(null)))
468 type_tag = JSONType.null_;
470 else static if (is(T : string))
472 type_tag = JSONType.string;
473 string t = arg;
474 () @trusted { store.str = t; }();
476 // https://issues.dlang.org/show_bug.cgi?id=15884
477 else static if (isSomeString!T)
479 type_tag = JSONType.string;
480 // FIXME: std.Array.Array(Range) is not deduced as 'pure'
481 () @trusted {
482 import std.utf : byUTF;
483 store.str = cast(immutable)(arg.byUTF!char.array);
484 }();
486 else static if (is(T : bool))
488 type_tag = arg ? JSONType.true_ : JSONType.false_;
490 else static if (is(T : ulong) && isUnsigned!T)
492 type_tag = JSONType.uinteger;
493 store.uinteger = arg;
495 else static if (is(T : long))
497 type_tag = JSONType.integer;
498 store.integer = arg;
500 else static if (isFloatingPoint!T)
502 type_tag = JSONType.float_;
503 store.floating = arg;
505 else static if (is(T : Value[Key], Key, Value))
507 static assert(is(Key : string), "AA key must be string");
508 type_tag = JSONType.object;
509 static if (is(Value : JSONValue))
511 JSONValue[string] t = arg;
512 () @trusted { store.object = t; }();
514 else
516 JSONValue[string] aa;
517 foreach (key, value; arg)
518 aa[key] = JSONValue(value);
519 () @trusted { store.object = aa; }();
522 else static if (isArray!T)
524 type_tag = JSONType.array;
525 static if (is(ElementEncodingType!T : JSONValue))
527 JSONValue[] t = arg;
528 () @trusted { store.array = t; }();
530 else
532 JSONValue[] new_arg = new JSONValue[arg.length];
533 foreach (i, e; arg)
534 new_arg[i] = JSONValue(e);
535 () @trusted { store.array = new_arg; }();
538 else static if (is(T : JSONValue))
540 type_tag = arg.type;
541 store = arg.store;
543 else
545 static assert(false, text(`unable to convert type "`, T.stringof, `" to json`));
549 private void assignRef(T)(ref T arg) if (isStaticArray!T)
551 type_tag = JSONType.array;
552 static if (is(ElementEncodingType!T : JSONValue))
554 store.array = arg;
556 else
558 JSONValue[] new_arg = new JSONValue[arg.length];
559 foreach (i, e; arg)
560 new_arg[i] = JSONValue(e);
561 store.array = new_arg;
566 * Constructor for `JSONValue`. If `arg` is a `JSONValue`
567 * its value and type will be copied to the new `JSONValue`.
568 * Note that this is a shallow copy: if type is `JSONType.object`
569 * or `JSONType.array` then only the reference to the data will
570 * be copied.
571 * Otherwise, `arg` must be implicitly convertible to one of the
572 * following types: `typeof(null)`, `string`, `ulong`,
573 * `long`, `double`, an associative array `V[K]` for any `V`
574 * and `K` i.e. a JSON object, any array or `bool`. The type will
575 * be set accordingly.
577 this(T)(T arg) if (!isStaticArray!T)
579 assign(arg);
581 /// Ditto
582 this(T)(ref T arg) if (isStaticArray!T)
584 assignRef(arg);
586 /// Ditto
587 this(T : JSONValue)(inout T arg) inout
589 store = arg.store;
590 type_tag = arg.type;
593 @safe unittest
595 JSONValue j = JSONValue( "a string" );
596 j = JSONValue(42);
598 j = JSONValue( [1, 2, 3] );
599 assert(j.type == JSONType.array);
601 j = JSONValue( ["language": "D"] );
602 assert(j.type == JSONType.object);
605 void opAssign(T)(T arg) if (!isStaticArray!T && !is(T : JSONValue))
607 assign(arg);
610 void opAssign(T)(ref T arg) if (isStaticArray!T)
612 assignRef(arg);
615 /***
616 * Array syntax for json arrays.
617 * Throws: `JSONException` if `type` is not `JSONType.array`.
619 ref inout(JSONValue) opIndex(size_t i) inout pure @safe
621 auto a = this.arrayNoRef;
622 enforce!JSONException(i < a.length,
623 "JSONValue array index is out of range");
624 return a[i];
627 @safe unittest
629 JSONValue j = JSONValue( [42, 43, 44] );
630 assert( j[0].integer == 42 );
631 assert( j[1].integer == 43 );
634 /***
635 * Hash syntax for json objects.
636 * Throws: `JSONException` if `type` is not `JSONType.object`.
638 ref inout(JSONValue) opIndex(return scope string k) inout pure @safe
640 auto o = this.objectNoRef;
641 return *enforce!JSONException(k in o,
642 "Key not found: " ~ k);
645 @safe unittest
647 JSONValue j = JSONValue( ["language": "D"] );
648 assert( j["language"].str == "D" );
651 /***
652 * Operator sets `value` for element of JSON object by `key`.
654 * If JSON value is null, then operator initializes it with object and then
655 * sets `value` for it.
657 * Throws: `JSONException` if `type` is not `JSONType.object`
658 * or `JSONType.null_`.
660 void opIndexAssign(T)(auto ref T value, string key)
662 enforce!JSONException(type == JSONType.object || type == JSONType.null_,
663 "JSONValue must be object or null");
664 JSONValue[string] aa = null;
665 if (type == JSONType.object)
667 aa = this.objectNoRef;
670 aa[key] = value;
671 this.object = aa;
674 @safe unittest
676 JSONValue j = JSONValue( ["language": "D"] );
677 j["language"].str = "Perl";
678 assert( j["language"].str == "Perl" );
681 void opIndexAssign(T)(T arg, size_t i)
683 auto a = this.arrayNoRef;
684 enforce!JSONException(i < a.length,
685 "JSONValue array index is out of range");
686 a[i] = arg;
687 this.array = a;
690 @safe unittest
692 JSONValue j = JSONValue( ["Perl", "C"] );
693 j[1].str = "D";
694 assert( j[1].str == "D" );
697 JSONValue opBinary(string op : "~", T)(T arg)
699 auto a = this.arrayNoRef;
700 static if (isArray!T)
702 return JSONValue(a ~ JSONValue(arg).arrayNoRef);
704 else static if (is(T : JSONValue))
706 return JSONValue(a ~ arg.arrayNoRef);
708 else
710 static assert(false, "argument is not an array or a JSONValue array");
714 void opOpAssign(string op : "~", T)(T arg)
716 auto a = this.arrayNoRef;
717 static if (isArray!T)
719 a ~= JSONValue(arg).arrayNoRef;
721 else static if (is(T : JSONValue))
723 a ~= arg.arrayNoRef;
725 else
727 static assert(false, "argument is not an array or a JSONValue array");
729 this.array = a;
733 * Support for the `in` operator.
735 * Tests wether a key can be found in an object.
737 * Returns:
738 * when found, the `inout(JSONValue)*` that matches to the key,
739 * otherwise `null`.
741 * Throws: `JSONException` if the right hand side argument `JSONType`
742 * is not `object`.
744 inout(JSONValue)* opBinaryRight(string op : "in")(string k) inout @safe
746 return k in this.objectNoRef;
749 @safe unittest
751 JSONValue j = [ "language": "D", "author": "walter" ];
752 string a = ("author" in j).str;
753 *("author" in j) = "Walter";
754 assert(j["author"].str == "Walter");
757 bool opEquals(const JSONValue rhs) const @nogc nothrow pure @safe
759 return opEquals(rhs);
762 bool opEquals(ref const JSONValue rhs) const @nogc nothrow pure @trusted
764 // Default doesn't work well since store is a union. Compare only
765 // what should be in store.
766 // This is @trusted to remain nogc, nothrow, fast, and usable from @safe code.
768 final switch (type_tag)
770 case JSONType.integer:
771 switch (rhs.type_tag)
773 case JSONType.integer:
774 return store.integer == rhs.store.integer;
775 case JSONType.uinteger:
776 return store.integer == rhs.store.uinteger;
777 case JSONType.float_:
778 return store.integer == rhs.store.floating;
779 default:
780 return false;
782 case JSONType.uinteger:
783 switch (rhs.type_tag)
785 case JSONType.integer:
786 return store.uinteger == rhs.store.integer;
787 case JSONType.uinteger:
788 return store.uinteger == rhs.store.uinteger;
789 case JSONType.float_:
790 return store.uinteger == rhs.store.floating;
791 default:
792 return false;
794 case JSONType.float_:
795 switch (rhs.type_tag)
797 case JSONType.integer:
798 return store.floating == rhs.store.integer;
799 case JSONType.uinteger:
800 return store.floating == rhs.store.uinteger;
801 case JSONType.float_:
802 return store.floating == rhs.store.floating;
803 default:
804 return false;
806 case JSONType.string:
807 return type_tag == rhs.type_tag && store.str == rhs.store.str;
808 case JSONType.object:
809 return type_tag == rhs.type_tag && store.object == rhs.store.object;
810 case JSONType.array:
811 return type_tag == rhs.type_tag && store.array == rhs.store.array;
812 case JSONType.true_:
813 case JSONType.false_:
814 case JSONType.null_:
815 return type_tag == rhs.type_tag;
820 @safe unittest
822 assert(JSONValue(0u) == JSONValue(0));
823 assert(JSONValue(0u) == JSONValue(0.0));
824 assert(JSONValue(0) == JSONValue(0.0));
827 /// Implements the foreach `opApply` interface for json arrays.
828 int opApply(scope int delegate(size_t index, ref JSONValue) dg) @system
830 int result;
832 foreach (size_t index, ref value; array)
834 result = dg(index, value);
835 if (result)
836 break;
839 return result;
842 /// Implements the foreach `opApply` interface for json objects.
843 int opApply(scope int delegate(string key, ref JSONValue) dg) @system
845 enforce!JSONException(type == JSONType.object,
846 "JSONValue is not an object");
847 int result;
849 foreach (string key, ref value; object)
851 result = dg(key, value);
852 if (result)
853 break;
856 return result;
859 /***
860 * Implicitly calls `toJSON` on this JSONValue.
862 * $(I options) can be used to tweak the conversion behavior.
864 string toString(in JSONOptions options = JSONOptions.none) const @safe
866 return toJSON(this, false, options);
870 void toString(Out)(Out sink, in JSONOptions options = JSONOptions.none) const
872 toJSON(sink, this, false, options);
875 /***
876 * Implicitly calls `toJSON` on this JSONValue, like `toString`, but
877 * also passes $(I true) as $(I pretty) argument.
879 * $(I options) can be used to tweak the conversion behavior
881 string toPrettyString(in JSONOptions options = JSONOptions.none) const @safe
883 return toJSON(this, true, options);
887 void toPrettyString(Out)(Out sink, in JSONOptions options = JSONOptions.none) const
889 toJSON(sink, this, true, options);
893 // https://issues.dlang.org/show_bug.cgi?id=20874
894 @system unittest
896 static struct MyCustomType
898 public string toString () const @system { return null; }
899 alias toString this;
902 static struct B
904 public JSONValue asJSON() const @system { return JSONValue.init; }
905 alias asJSON this;
908 if (false) // Just checking attributes
910 JSONValue json;
911 MyCustomType ilovedlang;
912 json = ilovedlang;
913 json["foo"] = ilovedlang;
914 auto s = ilovedlang in json;
916 B b;
917 json ~= b;
918 json ~ b;
923 Parses a serialized string and returns a tree of JSON values.
924 Throws: $(LREF JSONException) if string does not follow the JSON grammar or the depth exceeds the max depth,
925 $(LREF ConvException) if a number in the input cannot be represented by a native D type.
926 Params:
927 json = json-formatted string to parse
928 maxDepth = maximum depth of nesting allowed, -1 disables depth checking
929 options = enable decoding string representations of NaN/Inf as float values
931 JSONValue parseJSON(T)(T json, int maxDepth = -1, JSONOptions options = JSONOptions.none)
932 if (isSomeFiniteCharInputRange!T)
934 import std.ascii : isDigit, isHexDigit, toUpper, toLower;
935 import std.typecons : Nullable, Yes;
936 JSONValue root;
937 root.type_tag = JSONType.null_;
939 // Avoid UTF decoding when possible, as it is unnecessary when
940 // processing JSON.
941 static if (is(T : const(char)[]))
942 alias Char = char;
943 else
944 alias Char = Unqual!(ElementType!T);
946 int depth = -1;
947 Nullable!Char next;
948 int line = 1, pos = 0;
949 immutable bool strict = (options & JSONOptions.strictParsing) != 0;
951 void error(string msg)
953 throw new JSONException(msg, line, pos);
956 if (json.empty)
958 if (strict)
960 error("Empty JSON body");
962 return root;
965 bool isWhite(dchar c)
967 if (strict)
969 // RFC 7159 has a stricter definition of whitespace than general ASCII.
970 return c == ' ' || c == '\t' || c == '\n' || c == '\r';
972 import std.ascii : isWhite;
973 // Accept ASCII NUL as whitespace in non-strict mode.
974 return c == 0 || isWhite(c);
977 Char popChar()
979 if (json.empty) error("Unexpected end of data.");
980 static if (is(T : const(char)[]))
982 Char c = json[0];
983 json = json[1..$];
985 else
987 Char c = json.front;
988 json.popFront();
991 if (c == '\n')
993 line++;
994 pos = 0;
996 else
998 pos++;
1001 return c;
1004 Char peekChar()
1006 if (next.isNull)
1008 if (json.empty) return '\0';
1009 next = popChar();
1011 return next.get;
1014 Nullable!Char peekCharNullable()
1016 if (next.isNull && !json.empty)
1018 next = popChar();
1020 return next;
1023 void skipWhitespace()
1025 while (true)
1027 auto c = peekCharNullable();
1028 if (c.isNull ||
1029 !isWhite(c.get))
1031 return;
1033 next.nullify();
1037 Char getChar(bool SkipWhitespace = false)()
1039 static if (SkipWhitespace) skipWhitespace();
1041 Char c;
1042 if (!next.isNull)
1044 c = next.get;
1045 next.nullify();
1047 else
1048 c = popChar();
1050 return c;
1053 void checkChar(bool SkipWhitespace = true)(char c, bool caseSensitive = true)
1055 static if (SkipWhitespace) skipWhitespace();
1056 auto c2 = getChar();
1057 if (!caseSensitive) c2 = toLower(c2);
1059 if (c2 != c) error(text("Found '", c2, "' when expecting '", c, "'."));
1062 bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c)
1064 static if (SkipWhitespace) skipWhitespace();
1065 auto c2 = peekChar();
1066 static if (!CaseSensitive) c2 = toLower(c2);
1068 if (c2 != c) return false;
1070 getChar();
1071 return true;
1074 wchar parseWChar()
1076 wchar val = 0;
1077 foreach_reverse (i; 0 .. 4)
1079 auto hex = toUpper(getChar());
1080 if (!isHexDigit(hex)) error("Expecting hex character");
1081 val += (isDigit(hex) ? hex - '0' : hex - ('A' - 10)) << (4 * i);
1083 return val;
1086 string parseString()
1088 import std.uni : isSurrogateHi, isSurrogateLo;
1089 import std.utf : encode, decode;
1091 auto str = appender!string();
1093 Next:
1094 switch (peekChar())
1096 case '"':
1097 getChar();
1098 break;
1100 case '\\':
1101 getChar();
1102 auto c = getChar();
1103 switch (c)
1105 case '"': str.put('"'); break;
1106 case '\\': str.put('\\'); break;
1107 case '/': str.put('/'); break;
1108 case 'b': str.put('\b'); break;
1109 case 'f': str.put('\f'); break;
1110 case 'n': str.put('\n'); break;
1111 case 'r': str.put('\r'); break;
1112 case 't': str.put('\t'); break;
1113 case 'u':
1114 wchar wc = parseWChar();
1115 dchar val;
1116 // Non-BMP characters are escaped as a pair of
1117 // UTF-16 surrogate characters (see RFC 4627).
1118 if (isSurrogateHi(wc))
1120 wchar[2] pair;
1121 pair[0] = wc;
1122 if (getChar() != '\\') error("Expected escaped low surrogate after escaped high surrogate");
1123 if (getChar() != 'u') error("Expected escaped low surrogate after escaped high surrogate");
1124 pair[1] = parseWChar();
1125 size_t index = 0;
1126 val = decode(pair[], index);
1127 if (index != 2) error("Invalid escaped surrogate pair");
1129 else
1130 if (isSurrogateLo(wc))
1131 error(text("Unexpected low surrogate"));
1132 else
1133 val = wc;
1135 char[4] buf;
1136 immutable len = encode!(Yes.useReplacementDchar)(buf, val);
1137 str.put(buf[0 .. len]);
1138 break;
1140 default:
1141 error(text("Invalid escape sequence '\\", c, "'."));
1143 goto Next;
1145 default:
1146 // RFC 7159 states that control characters U+0000 through
1147 // U+001F must not appear unescaped in a JSON string.
1148 // Note: std.ascii.isControl can't be used for this test
1149 // because it considers ASCII DEL (0x7f) to be a control
1150 // character but RFC 7159 does not.
1151 // Accept unescaped ASCII NULs in non-strict mode.
1152 auto c = getChar();
1153 if (c < 0x20 && (strict || c != 0))
1154 error("Illegal control character.");
1155 str.put(c);
1156 goto Next;
1159 return str.data.length ? str.data : "";
1162 bool tryGetSpecialFloat(string str, out double val) {
1163 switch (str)
1165 case JSONFloatLiteral.nan:
1166 val = double.nan;
1167 return true;
1168 case JSONFloatLiteral.inf:
1169 val = double.infinity;
1170 return true;
1171 case JSONFloatLiteral.negativeInf:
1172 val = -double.infinity;
1173 return true;
1174 default:
1175 return false;
1179 void parseValue(ref JSONValue value)
1181 depth++;
1183 if (maxDepth != -1 && depth > maxDepth) error("Nesting too deep.");
1185 auto c = getChar!true();
1187 switch (c)
1189 case '{':
1190 if (testChar('}'))
1192 value.object = null;
1193 break;
1196 JSONValue[string] obj;
1199 skipWhitespace();
1200 if (!strict && peekChar() == '}')
1202 break;
1204 checkChar('"');
1205 string name = parseString();
1206 checkChar(':');
1207 JSONValue member;
1208 parseValue(member);
1209 obj[name] = member;
1211 while (testChar(','));
1212 value.object = obj;
1214 checkChar('}');
1215 break;
1217 case '[':
1218 if (testChar(']'))
1220 value.type_tag = JSONType.array;
1221 break;
1224 JSONValue[] arr;
1227 skipWhitespace();
1228 if (!strict && peekChar() == ']')
1230 break;
1232 JSONValue element;
1233 parseValue(element);
1234 arr ~= element;
1236 while (testChar(','));
1238 checkChar(']');
1239 value.array = arr;
1240 break;
1242 case '"':
1243 auto str = parseString();
1245 // if special float parsing is enabled, check if string represents NaN/Inf
1246 if ((options & JSONOptions.specialFloatLiterals) &&
1247 tryGetSpecialFloat(str, value.store.floating))
1249 // found a special float, its value was placed in value.store.floating
1250 value.type_tag = JSONType.float_;
1251 break;
1254 value.assign(str);
1255 break;
1257 case '0': .. case '9':
1258 case '-':
1259 auto number = appender!string();
1260 bool isFloat, isNegative;
1262 void readInteger()
1264 if (!isDigit(c)) error("Digit expected");
1266 Next: number.put(c);
1268 if (isDigit(peekChar()))
1270 c = getChar();
1271 goto Next;
1275 if (c == '-')
1277 number.put('-');
1278 c = getChar();
1279 isNegative = true;
1282 if (strict && c == '0')
1284 number.put('0');
1285 if (isDigit(peekChar()))
1287 error("Additional digits not allowed after initial zero digit");
1290 else
1292 readInteger();
1295 if (testChar('.'))
1297 isFloat = true;
1298 number.put('.');
1299 c = getChar();
1300 readInteger();
1302 if (testChar!(false, false)('e'))
1304 isFloat = true;
1305 number.put('e');
1306 if (testChar('+')) number.put('+');
1307 else if (testChar('-')) number.put('-');
1308 c = getChar();
1309 readInteger();
1312 string data = number.data;
1313 if (isFloat)
1315 value.type_tag = JSONType.float_;
1316 value.store.floating = parse!double(data);
1318 else
1320 if (isNegative)
1322 value.store.integer = parse!long(data);
1323 value.type_tag = JSONType.integer;
1325 else
1327 // only set the correct union member to not confuse CTFE
1328 ulong u = parse!ulong(data);
1329 if (u & (1UL << 63))
1331 value.store.uinteger = u;
1332 value.type_tag = JSONType.uinteger;
1334 else
1336 value.store.integer = u;
1337 value.type_tag = JSONType.integer;
1341 break;
1343 case 'T':
1344 if (strict) goto default;
1345 goto case;
1346 case 't':
1347 value.type_tag = JSONType.true_;
1348 checkChar!false('r', strict);
1349 checkChar!false('u', strict);
1350 checkChar!false('e', strict);
1351 break;
1353 case 'F':
1354 if (strict) goto default;
1355 goto case;
1356 case 'f':
1357 value.type_tag = JSONType.false_;
1358 checkChar!false('a', strict);
1359 checkChar!false('l', strict);
1360 checkChar!false('s', strict);
1361 checkChar!false('e', strict);
1362 break;
1364 case 'N':
1365 if (strict) goto default;
1366 goto case;
1367 case 'n':
1368 value.type_tag = JSONType.null_;
1369 checkChar!false('u', strict);
1370 checkChar!false('l', strict);
1371 checkChar!false('l', strict);
1372 break;
1374 default:
1375 error(text("Unexpected character '", c, "'."));
1378 depth--;
1381 parseValue(root);
1382 if (strict)
1384 skipWhitespace();
1385 if (!peekCharNullable().isNull) error("Trailing non-whitespace characters");
1387 return root;
1390 @safe unittest
1392 enum issue15742objectOfObject = `{ "key1": { "key2": 1 }}`;
1393 static assert(parseJSON(issue15742objectOfObject).type == JSONType.object);
1395 enum issue15742arrayOfArray = `[[1]]`;
1396 static assert(parseJSON(issue15742arrayOfArray).type == JSONType.array);
1399 @safe unittest
1401 // Ensure we can parse and use JSON from @safe code
1402 auto a = `{ "key1": { "key2": 1 }}`.parseJSON;
1403 assert(a["key1"]["key2"].integer == 1);
1404 assert(a.toString == `{"key1":{"key2":1}}`);
1407 @system unittest
1409 // Ensure we can parse JSON from a @system range.
1410 struct Range
1412 string s;
1413 size_t index;
1414 @system
1416 bool empty() { return index >= s.length; }
1417 void popFront() { index++; }
1418 char front() { return s[index]; }
1421 auto s = Range(`{ "key1": { "key2": 1 }}`);
1422 auto json = parseJSON(s);
1423 assert(json["key1"]["key2"].integer == 1);
1426 // https://issues.dlang.org/show_bug.cgi?id=20527
1427 @safe unittest
1429 static assert(parseJSON(`{"a" : 2}`)["a"].integer == 2);
1433 Parses a serialized string and returns a tree of JSON values.
1434 Throws: $(LREF JSONException) if the depth exceeds the max depth.
1435 Params:
1436 json = json-formatted string to parse
1437 options = enable decoding string representations of NaN/Inf as float values
1439 JSONValue parseJSON(T)(T json, JSONOptions options)
1440 if (isSomeFiniteCharInputRange!T)
1442 return parseJSON!T(json, -1, options);
1446 Takes a tree of JSON values and returns the serialized string.
1448 Any Object types will be serialized in a key-sorted order.
1450 If `pretty` is false no whitespaces are generated.
1451 If `pretty` is true serialized string is formatted to be human-readable.
1452 Set the $(LREF JSONOptions.specialFloatLiterals) flag is set in `options` to encode NaN/Infinity as strings.
1454 string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions options = JSONOptions.none) @safe
1456 auto json = appender!string();
1457 toJSON(json, root, pretty, options);
1458 return json.data;
1462 void toJSON(Out)(
1463 auto ref Out json,
1464 const ref JSONValue root,
1465 in bool pretty = false,
1466 in JSONOptions options = JSONOptions.none)
1467 if (isOutputRange!(Out,char))
1469 void toStringImpl(Char)(string str)
1471 json.put('"');
1473 foreach (Char c; str)
1475 switch (c)
1477 case '"': json.put("\\\""); break;
1478 case '\\': json.put("\\\\"); break;
1480 case '/':
1481 if (!(options & JSONOptions.doNotEscapeSlashes))
1482 json.put('\\');
1483 json.put('/');
1484 break;
1486 case '\b': json.put("\\b"); break;
1487 case '\f': json.put("\\f"); break;
1488 case '\n': json.put("\\n"); break;
1489 case '\r': json.put("\\r"); break;
1490 case '\t': json.put("\\t"); break;
1491 default:
1493 import std.ascii : isControl;
1494 import std.utf : encode;
1496 // Make sure we do UTF decoding iff we want to
1497 // escape Unicode characters.
1498 assert(((options & JSONOptions.escapeNonAsciiChars) != 0)
1499 == is(Char == dchar), "JSONOptions.escapeNonAsciiChars needs dchar strings");
1501 with (JSONOptions) if (isControl(c) ||
1502 ((options & escapeNonAsciiChars) >= escapeNonAsciiChars && c >= 0x80))
1504 // Ensure non-BMP characters are encoded as a pair
1505 // of UTF-16 surrogate characters, as per RFC 4627.
1506 wchar[2] wchars; // 1 or 2 UTF-16 code units
1507 size_t wNum = encode(wchars, c); // number of UTF-16 code units
1508 foreach (wc; wchars[0 .. wNum])
1510 json.put("\\u");
1511 foreach_reverse (i; 0 .. 4)
1513 char ch = (wc >>> (4 * i)) & 0x0f;
1514 ch += ch < 10 ? '0' : 'A' - 10;
1515 json.put(ch);
1519 else
1521 json.put(c);
1527 json.put('"');
1530 void toString(string str)
1532 // Avoid UTF decoding when possible, as it is unnecessary when
1533 // processing JSON.
1534 if (options & JSONOptions.escapeNonAsciiChars)
1535 toStringImpl!dchar(str);
1536 else
1537 toStringImpl!char(str);
1540 // recursive @safe inference is broken here
1541 // workaround: if json.put is @safe, we should be too,
1542 // so annotate the recursion as @safe manually
1543 static if (isSafe!({ json.put(""); }))
1545 void delegate(ref const JSONValue, ulong) @safe toValue;
1547 else
1549 void delegate(ref const JSONValue, ulong) @system toValue;
1552 void toValueImpl(ref const JSONValue value, ulong indentLevel)
1554 void putTabs(ulong additionalIndent = 0)
1556 if (pretty)
1557 foreach (i; 0 .. indentLevel + additionalIndent)
1558 json.put(" ");
1560 void putEOL()
1562 if (pretty)
1563 json.put('\n');
1565 void putCharAndEOL(char ch)
1567 json.put(ch);
1568 putEOL();
1571 final switch (value.type)
1573 case JSONType.object:
1574 auto obj = value.objectNoRef;
1575 if (!obj.length)
1577 json.put("{}");
1579 else
1581 putCharAndEOL('{');
1582 bool first = true;
1584 void emit(R)(R names)
1586 foreach (name; names)
1588 auto member = obj[name];
1589 if (!first)
1590 putCharAndEOL(',');
1591 first = false;
1592 putTabs(1);
1593 toString(name);
1594 json.put(':');
1595 if (pretty)
1596 json.put(' ');
1597 toValue(member, indentLevel + 1);
1601 import std.algorithm.sorting : sort;
1602 // https://issues.dlang.org/show_bug.cgi?id=14439
1603 // auto names = obj.keys; // aa.keys can't be called in @safe code
1604 auto names = new string[obj.length];
1605 size_t i = 0;
1606 foreach (k, v; obj)
1608 names[i] = k;
1609 i++;
1611 sort(names);
1612 emit(names);
1614 putEOL();
1615 putTabs();
1616 json.put('}');
1618 break;
1620 case JSONType.array:
1621 auto arr = value.arrayNoRef;
1622 if (arr.empty)
1624 json.put("[]");
1626 else
1628 putCharAndEOL('[');
1629 foreach (i, el; arr)
1631 if (i)
1632 putCharAndEOL(',');
1633 putTabs(1);
1634 toValue(el, indentLevel + 1);
1636 putEOL();
1637 putTabs();
1638 json.put(']');
1640 break;
1642 case JSONType.string:
1643 toString(value.str);
1644 break;
1646 case JSONType.integer:
1647 json.put(to!string(value.store.integer));
1648 break;
1650 case JSONType.uinteger:
1651 json.put(to!string(value.store.uinteger));
1652 break;
1654 case JSONType.float_:
1655 import std.math.traits : isNaN, isInfinity;
1657 auto val = value.store.floating;
1659 if (val.isNaN)
1661 if (options & JSONOptions.specialFloatLiterals)
1663 toString(JSONFloatLiteral.nan);
1665 else
1667 throw new JSONException(
1668 "Cannot encode NaN. Consider passing the specialFloatLiterals flag.");
1671 else if (val.isInfinity)
1673 if (options & JSONOptions.specialFloatLiterals)
1675 toString((val > 0) ? JSONFloatLiteral.inf : JSONFloatLiteral.negativeInf);
1677 else
1679 throw new JSONException(
1680 "Cannot encode Infinity. Consider passing the specialFloatLiterals flag.");
1683 else
1685 import std.algorithm.searching : canFind;
1686 import std.format : sformat;
1687 // The correct formula for the number of decimal digits needed for lossless round
1688 // trips is actually:
1689 // ceil(log(pow(2.0, double.mant_dig - 1)) / log(10.0) + 1) == (double.dig + 2)
1690 // Anything less will round off (1 + double.epsilon)
1691 char[25] buf;
1692 auto result = buf[].sformat!"%.18g"(val);
1693 json.put(result);
1694 if (!result.canFind('e') && !result.canFind('.'))
1695 json.put(".0");
1697 break;
1699 case JSONType.true_:
1700 json.put("true");
1701 break;
1703 case JSONType.false_:
1704 json.put("false");
1705 break;
1707 case JSONType.null_:
1708 json.put("null");
1709 break;
1713 toValue = &toValueImpl;
1715 toValue(root, 0);
1718 // https://issues.dlang.org/show_bug.cgi?id=12897
1719 @safe unittest
1721 JSONValue jv0 = JSONValue("test测试");
1722 assert(toJSON(jv0, false, JSONOptions.escapeNonAsciiChars) == `"test\u6D4B\u8BD5"`);
1723 JSONValue jv00 = JSONValue("test\u6D4B\u8BD5");
1724 assert(toJSON(jv00, false, JSONOptions.none) == `"test测试"`);
1725 assert(toJSON(jv0, false, JSONOptions.none) == `"test测试"`);
1726 JSONValue jv1 = JSONValue("été");
1727 assert(toJSON(jv1, false, JSONOptions.escapeNonAsciiChars) == `"\u00E9t\u00E9"`);
1728 JSONValue jv11 = JSONValue("\u00E9t\u00E9");
1729 assert(toJSON(jv11, false, JSONOptions.none) == `"été"`);
1730 assert(toJSON(jv1, false, JSONOptions.none) == `"été"`);
1733 // https://issues.dlang.org/show_bug.cgi?id=20511
1734 @system unittest
1736 import std.format.write : formattedWrite;
1737 import std.range : nullSink, outputRangeObject;
1739 outputRangeObject!(const(char)[])(nullSink)
1740 .formattedWrite!"%s"(JSONValue.init);
1743 // Issue 16432 - JSON incorrectly parses to string
1744 @safe unittest
1746 // Floating points numbers are rounded to the nearest integer and thus get
1747 // incorrectly parsed
1749 import std.math.operations : isClose;
1751 string s = "{\"rating\": 3.0 }";
1752 JSONValue j = parseJSON(s);
1753 assert(j["rating"].type == JSONType.float_);
1754 j = j.toString.parseJSON;
1755 assert(j["rating"].type == JSONType.float_);
1756 assert(isClose(j["rating"].floating, 3.0));
1758 s = "{\"rating\": -3.0 }";
1759 j = parseJSON(s);
1760 assert(j["rating"].type == JSONType.float_);
1761 j = j.toString.parseJSON;
1762 assert(j["rating"].type == JSONType.float_);
1763 assert(isClose(j["rating"].floating, -3.0));
1765 // https://issues.dlang.org/show_bug.cgi?id=13660
1766 auto jv1 = JSONValue(4.0);
1767 auto textual = jv1.toString();
1768 auto jv2 = parseJSON(textual);
1769 assert(jv1.type == JSONType.float_);
1770 assert(textual == "4.0");
1771 assert(jv2.type == JSONType.float_);
1774 @safe unittest
1776 // Adapted from https://github.com/dlang/phobos/pull/5005
1777 // Result from toString is not checked here, because this
1778 // might differ (%e-like or %f-like output) depending
1779 // on OS and compiler optimization.
1780 import std.math.operations : isClose;
1782 // test positive extreme values
1783 JSONValue j;
1784 j["rating"] = 1e18 - 65;
1785 assert(isClose(j.toString.parseJSON["rating"].floating, 1e18 - 65));
1787 j["rating"] = 1e18 - 64;
1788 assert(isClose(j.toString.parseJSON["rating"].floating, 1e18 - 64));
1790 // negative extreme values
1791 j["rating"] = -1e18 + 65;
1792 assert(isClose(j.toString.parseJSON["rating"].floating, -1e18 + 65));
1794 j["rating"] = -1e18 + 64;
1795 assert(isClose(j.toString.parseJSON["rating"].floating, -1e18 + 64));
1799 Exception thrown on JSON errors
1801 class JSONException : Exception
1803 this(string msg, int line = 0, int pos = 0) pure nothrow @safe
1805 if (line)
1806 super(text(msg, " (Line ", line, ":", pos, ")"));
1807 else
1808 super(msg);
1811 this(string msg, string file, size_t line) pure nothrow @safe
1813 super(msg, file, line);
1818 @system unittest
1820 import std.exception;
1821 JSONValue jv = "123";
1822 assert(jv.type == JSONType.string);
1823 assertNotThrown(jv.str);
1824 assertThrown!JSONException(jv.integer);
1825 assertThrown!JSONException(jv.uinteger);
1826 assertThrown!JSONException(jv.floating);
1827 assertThrown!JSONException(jv.object);
1828 assertThrown!JSONException(jv.array);
1829 assertThrown!JSONException(jv["aa"]);
1830 assertThrown!JSONException(jv[2]);
1832 jv = -3;
1833 assert(jv.type == JSONType.integer);
1834 assertNotThrown(jv.integer);
1836 jv = cast(uint) 3;
1837 assert(jv.type == JSONType.uinteger);
1838 assertNotThrown(jv.uinteger);
1840 jv = 3.0;
1841 assert(jv.type == JSONType.float_);
1842 assertNotThrown(jv.floating);
1844 jv = ["key" : "value"];
1845 assert(jv.type == JSONType.object);
1846 assertNotThrown(jv.object);
1847 assertNotThrown(jv["key"]);
1848 assert("key" in jv);
1849 assert("notAnElement" !in jv);
1850 assertThrown!JSONException(jv["notAnElement"]);
1851 const cjv = jv;
1852 assert("key" in cjv);
1853 assertThrown!JSONException(cjv["notAnElement"]);
1855 foreach (string key, value; jv)
1857 static assert(is(typeof(value) == JSONValue));
1858 assert(key == "key");
1859 assert(value.type == JSONType.string);
1860 assertNotThrown(value.str);
1861 assert(value.str == "value");
1864 jv = [3, 4, 5];
1865 assert(jv.type == JSONType.array);
1866 assertNotThrown(jv.array);
1867 assertNotThrown(jv[2]);
1868 foreach (size_t index, value; jv)
1870 static assert(is(typeof(value) == JSONValue));
1871 assert(value.type == JSONType.integer);
1872 assertNotThrown(value.integer);
1873 assert(index == (value.integer-3));
1876 jv = null;
1877 assert(jv.type == JSONType.null_);
1878 assert(jv.isNull);
1879 jv = "foo";
1880 assert(!jv.isNull);
1882 jv = JSONValue("value");
1883 assert(jv.type == JSONType.string);
1884 assert(jv.str == "value");
1886 JSONValue jv2 = JSONValue("value");
1887 assert(jv2.type == JSONType.string);
1888 assert(jv2.str == "value");
1890 JSONValue jv3 = JSONValue("\u001c");
1891 assert(jv3.type == JSONType.string);
1892 assert(jv3.str == "\u001C");
1895 // https://issues.dlang.org/show_bug.cgi?id=11504
1896 @system unittest
1898 JSONValue jv = 1;
1899 assert(jv.type == JSONType.integer);
1901 jv.str = "123";
1902 assert(jv.type == JSONType.string);
1903 assert(jv.str == "123");
1905 jv.integer = 1;
1906 assert(jv.type == JSONType.integer);
1907 assert(jv.integer == 1);
1909 jv.uinteger = 2u;
1910 assert(jv.type == JSONType.uinteger);
1911 assert(jv.uinteger == 2u);
1913 jv.floating = 1.5;
1914 assert(jv.type == JSONType.float_);
1915 assert(jv.floating == 1.5);
1917 jv.object = ["key" : JSONValue("value")];
1918 assert(jv.type == JSONType.object);
1919 assert(jv.object == ["key" : JSONValue("value")]);
1921 jv.array = [JSONValue(1), JSONValue(2), JSONValue(3)];
1922 assert(jv.type == JSONType.array);
1923 assert(jv.array == [JSONValue(1), JSONValue(2), JSONValue(3)]);
1925 jv = true;
1926 assert(jv.type == JSONType.true_);
1928 jv = false;
1929 assert(jv.type == JSONType.false_);
1931 enum E{True = true}
1932 jv = E.True;
1933 assert(jv.type == JSONType.true_);
1936 @system pure unittest
1938 // Adding new json element via array() / object() directly
1940 JSONValue jarr = JSONValue([10]);
1941 foreach (i; 0 .. 9)
1942 jarr.array ~= JSONValue(i);
1943 assert(jarr.array.length == 10);
1945 JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
1946 foreach (i; 0 .. 9)
1947 jobj.object[text("key", i)] = JSONValue(text("value", i));
1948 assert(jobj.object.length == 10);
1951 @system pure unittest
1953 // Adding new json element without array() / object() access
1955 JSONValue jarr = JSONValue([10]);
1956 foreach (i; 0 .. 9)
1957 jarr ~= [JSONValue(i)];
1958 assert(jarr.array.length == 10);
1960 JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
1961 foreach (i; 0 .. 9)
1962 jobj[text("key", i)] = JSONValue(text("value", i));
1963 assert(jobj.object.length == 10);
1965 // No array alias
1966 auto jarr2 = jarr ~ [1,2,3];
1967 jarr2[0] = 999;
1968 assert(jarr[0] == JSONValue(10));
1971 @system unittest
1973 // @system because JSONValue.array is @system
1974 import std.exception;
1976 // An overly simple test suite, if it can parse a serializated string and
1977 // then use the resulting values tree to generate an identical
1978 // serialization, both the decoder and encoder works.
1980 auto jsons = [
1981 `null`,
1982 `true`,
1983 `false`,
1984 `0`,
1985 `123`,
1986 `-4321`,
1987 `0.25`,
1988 `-0.25`,
1989 `""`,
1990 `"hello\nworld"`,
1991 `"\"\\\/\b\f\n\r\t"`,
1992 `[]`,
1993 `[12,"foo",true,false]`,
1994 `{}`,
1995 `{"a":1,"b":null}`,
1996 `{"goodbye":[true,"or",false,["test",42,{"nested":{"a":23.5,"b":0.140625}}]],`
1997 ~`"hello":{"array":[12,null,{}],"json":"is great"}}`,
2000 enum dbl1_844 = `1.8446744073709568`;
2001 version (MinGW)
2002 jsons ~= dbl1_844 ~ `e+019`;
2003 else
2004 jsons ~= dbl1_844 ~ `e+19`;
2006 JSONValue val;
2007 string result;
2008 foreach (json; jsons)
2012 val = parseJSON(json);
2013 enum pretty = false;
2014 result = toJSON(val, pretty);
2015 assert(result == json, text(result, " should be ", json));
2017 catch (JSONException e)
2019 import std.stdio : writefln;
2020 writefln(text(json, "\n", e.toString()));
2024 // Should be able to correctly interpret unicode entities
2025 val = parseJSON(`"\u003C\u003E"`);
2026 assert(toJSON(val) == "\"\&lt;\&gt;\"");
2027 assert(val.to!string() == "\"\&lt;\&gt;\"");
2028 val = parseJSON(`"\u0391\u0392\u0393"`);
2029 assert(toJSON(val) == "\"\&Alpha;\&Beta;\&Gamma;\"");
2030 assert(val.to!string() == "\"\&Alpha;\&Beta;\&Gamma;\"");
2031 val = parseJSON(`"\u2660\u2666"`);
2032 assert(toJSON(val) == "\"\&spades;\&diams;\"");
2033 assert(val.to!string() == "\"\&spades;\&diams;\"");
2035 //0x7F is a control character (see Unicode spec)
2036 val = parseJSON(`"\u007F"`);
2037 assert(toJSON(val) == "\"\\u007F\"");
2038 assert(val.to!string() == "\"\\u007F\"");
2040 with(parseJSON(`""`))
2041 assert(str == "" && str !is null);
2042 with(parseJSON(`[]`))
2043 assert(!array.length);
2045 // Formatting
2046 val = parseJSON(`{"a":[null,{"x":1},{},[]]}`);
2047 assert(toJSON(val, true) == `{
2048 "a": [
2049 null,
2051 "x": 1
2056 }`);
2059 @safe unittest
2061 auto json = `"hello\nworld"`;
2062 const jv = parseJSON(json);
2063 assert(jv.toString == json);
2064 assert(jv.toPrettyString == json);
2067 @system pure unittest
2069 // https://issues.dlang.org/show_bug.cgi?id=12969
2071 JSONValue jv;
2072 jv["int"] = 123;
2074 assert(jv.type == JSONType.object);
2075 assert("int" in jv);
2076 assert(jv["int"].integer == 123);
2078 jv["array"] = [1, 2, 3, 4, 5];
2080 assert(jv["array"].type == JSONType.array);
2081 assert(jv["array"][2].integer == 3);
2083 jv["str"] = "D language";
2084 assert(jv["str"].type == JSONType.string);
2085 assert(jv["str"].str == "D language");
2087 jv["bool"] = false;
2088 assert(jv["bool"].type == JSONType.false_);
2090 assert(jv.object.length == 4);
2092 jv = [5, 4, 3, 2, 1];
2093 assert(jv.type == JSONType.array);
2094 assert(jv[3].integer == 2);
2097 @safe unittest
2099 auto s = q"EOF
2104 potato
2106 EOF";
2108 import std.exception;
2110 auto e = collectException!JSONException(parseJSON(s));
2111 assert(e.msg == "Unexpected character 'p'. (Line 5:3)", e.msg);
2114 // handling of special float values (NaN, Inf, -Inf)
2115 @safe unittest
2117 import std.exception : assertThrown;
2118 import std.math.traits : isNaN, isInfinity;
2120 // expected representations of NaN and Inf
2121 enum {
2122 nanString = '"' ~ JSONFloatLiteral.nan ~ '"',
2123 infString = '"' ~ JSONFloatLiteral.inf ~ '"',
2124 negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"',
2127 // with the specialFloatLiterals option, encode NaN/Inf as strings
2128 assert(JSONValue(float.nan).toString(JSONOptions.specialFloatLiterals) == nanString);
2129 assert(JSONValue(double.infinity).toString(JSONOptions.specialFloatLiterals) == infString);
2130 assert(JSONValue(-real.infinity).toString(JSONOptions.specialFloatLiterals) == negativeInfString);
2132 // without the specialFloatLiterals option, throw on encoding NaN/Inf
2133 assertThrown!JSONException(JSONValue(float.nan).toString);
2134 assertThrown!JSONException(JSONValue(double.infinity).toString);
2135 assertThrown!JSONException(JSONValue(-real.infinity).toString);
2137 // when parsing json with specialFloatLiterals option, decode special strings as floats
2138 JSONValue jvNan = parseJSON(nanString, JSONOptions.specialFloatLiterals);
2139 JSONValue jvInf = parseJSON(infString, JSONOptions.specialFloatLiterals);
2140 JSONValue jvNegInf = parseJSON(negativeInfString, JSONOptions.specialFloatLiterals);
2142 assert(jvNan.floating.isNaN);
2143 assert(jvInf.floating.isInfinity && jvInf.floating > 0);
2144 assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0);
2146 // when parsing json without the specialFloatLiterals option, decode special strings as strings
2147 jvNan = parseJSON(nanString);
2148 jvInf = parseJSON(infString);
2149 jvNegInf = parseJSON(negativeInfString);
2151 assert(jvNan.str == JSONFloatLiteral.nan);
2152 assert(jvInf.str == JSONFloatLiteral.inf);
2153 assert(jvNegInf.str == JSONFloatLiteral.negativeInf);
2156 pure nothrow @safe @nogc unittest
2158 JSONValue testVal;
2159 testVal = "test";
2160 testVal = 10;
2161 testVal = 10u;
2162 testVal = 1.0;
2163 testVal = (JSONValue[string]).init;
2164 testVal = JSONValue[].init;
2165 testVal = null;
2166 assert(testVal.isNull);
2169 // https://issues.dlang.org/show_bug.cgi?id=15884
2170 pure nothrow @safe unittest
2172 import std.typecons;
2173 void Test(C)() {
2174 C[] a = ['x'];
2175 JSONValue testVal = a;
2176 assert(testVal.type == JSONType.string);
2177 testVal = a.idup;
2178 assert(testVal.type == JSONType.string);
2180 Test!char();
2181 Test!wchar();
2182 Test!dchar();
2185 // https://issues.dlang.org/show_bug.cgi?id=15885
2186 @safe unittest
2188 enum bool realInDoublePrecision = real.mant_dig == double.mant_dig;
2190 static bool test(const double num0)
2192 import std.math.operations : feqrel;
2193 const json0 = JSONValue(num0);
2194 const num1 = to!double(toJSON(json0));
2195 static if (realInDoublePrecision)
2196 return feqrel(num1, num0) >= (double.mant_dig - 1);
2197 else
2198 return num1 == num0;
2201 assert(test( 0.23));
2202 assert(test(-0.23));
2203 assert(test(1.223e+24));
2204 assert(test(23.4));
2205 assert(test(0.0012));
2206 assert(test(30738.22));
2208 assert(test(1 + double.epsilon));
2209 assert(test(double.min_normal));
2210 static if (realInDoublePrecision)
2211 assert(test(-double.max / 2));
2212 else
2213 assert(test(-double.max));
2215 const minSub = double.min_normal * double.epsilon;
2216 assert(test(minSub));
2217 assert(test(3*minSub));
2220 // https://issues.dlang.org/show_bug.cgi?id=17555
2221 @safe unittest
2223 import std.exception : assertThrown;
2225 assertThrown!JSONException(parseJSON("\"a\nb\""));
2228 // https://issues.dlang.org/show_bug.cgi?id=17556
2229 @safe unittest
2231 auto v = JSONValue("\U0001D11E");
2232 auto j = toJSON(v, false, JSONOptions.escapeNonAsciiChars);
2233 assert(j == `"\uD834\uDD1E"`);
2236 // https://issues.dlang.org/show_bug.cgi?id=5904
2237 @safe unittest
2239 string s = `"\uD834\uDD1E"`;
2240 auto j = parseJSON(s);
2241 assert(j.str == "\U0001D11E");
2244 // https://issues.dlang.org/show_bug.cgi?id=17557
2245 @safe unittest
2247 assert(parseJSON("\"\xFF\"").str == "\xFF");
2248 assert(parseJSON("\"\U0001D11E\"").str == "\U0001D11E");
2251 // https://issues.dlang.org/show_bug.cgi?id=17553
2252 @safe unittest
2254 auto v = JSONValue("\xFF");
2255 assert(toJSON(v) == "\"\xFF\"");
2258 @safe unittest
2260 import std.utf;
2261 assert(parseJSON("\"\xFF\"".byChar).str == "\xFF");
2262 assert(parseJSON("\"\U0001D11E\"".byChar).str == "\U0001D11E");
2265 // JSONOptions.doNotEscapeSlashes (https://issues.dlang.org/show_bug.cgi?id=17587)
2266 @safe unittest
2268 assert(parseJSON(`"/"`).toString == `"\/"`);
2269 assert(parseJSON(`"\/"`).toString == `"\/"`);
2270 assert(parseJSON(`"/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
2271 assert(parseJSON(`"\/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
2274 // JSONOptions.strictParsing (https://issues.dlang.org/show_bug.cgi?id=16639)
2275 @safe unittest
2277 import std.exception : assertThrown;
2279 // Unescaped ASCII NULs
2280 assert(parseJSON("[\0]").type == JSONType.array);
2281 assertThrown!JSONException(parseJSON("[\0]", JSONOptions.strictParsing));
2282 assert(parseJSON("\"\0\"").str == "\0");
2283 assertThrown!JSONException(parseJSON("\"\0\"", JSONOptions.strictParsing));
2285 // Unescaped ASCII DEL (0x7f) in strings
2286 assert(parseJSON("\"\x7f\"").str == "\x7f");
2287 assert(parseJSON("\"\x7f\"", JSONOptions.strictParsing).str == "\x7f");
2289 // "true", "false", "null" case sensitivity
2290 assert(parseJSON("true").type == JSONType.true_);
2291 assert(parseJSON("true", JSONOptions.strictParsing).type == JSONType.true_);
2292 assert(parseJSON("True").type == JSONType.true_);
2293 assertThrown!JSONException(parseJSON("True", JSONOptions.strictParsing));
2294 assert(parseJSON("tRUE").type == JSONType.true_);
2295 assertThrown!JSONException(parseJSON("tRUE", JSONOptions.strictParsing));
2297 assert(parseJSON("false").type == JSONType.false_);
2298 assert(parseJSON("false", JSONOptions.strictParsing).type == JSONType.false_);
2299 assert(parseJSON("False").type == JSONType.false_);
2300 assertThrown!JSONException(parseJSON("False", JSONOptions.strictParsing));
2301 assert(parseJSON("fALSE").type == JSONType.false_);
2302 assertThrown!JSONException(parseJSON("fALSE", JSONOptions.strictParsing));
2304 assert(parseJSON("null").type == JSONType.null_);
2305 assert(parseJSON("null", JSONOptions.strictParsing).type == JSONType.null_);
2306 assert(parseJSON("Null").type == JSONType.null_);
2307 assertThrown!JSONException(parseJSON("Null", JSONOptions.strictParsing));
2308 assert(parseJSON("nULL").type == JSONType.null_);
2309 assertThrown!JSONException(parseJSON("nULL", JSONOptions.strictParsing));
2311 // Whitespace characters
2312 assert(parseJSON("[\f\v]").type == JSONType.array);
2313 assertThrown!JSONException(parseJSON("[\f\v]", JSONOptions.strictParsing));
2314 assert(parseJSON("[ \t\r\n]").type == JSONType.array);
2315 assert(parseJSON("[ \t\r\n]", JSONOptions.strictParsing).type == JSONType.array);
2317 // Empty input
2318 assert(parseJSON("").type == JSONType.null_);
2319 assertThrown!JSONException(parseJSON("", JSONOptions.strictParsing));
2321 // Numbers with leading '0's
2322 assert(parseJSON("01").integer == 1);
2323 assertThrown!JSONException(parseJSON("01", JSONOptions.strictParsing));
2324 assert(parseJSON("-01").integer == -1);
2325 assertThrown!JSONException(parseJSON("-01", JSONOptions.strictParsing));
2326 assert(parseJSON("0.01").floating == 0.01);
2327 assert(parseJSON("0.01", JSONOptions.strictParsing).floating == 0.01);
2328 assert(parseJSON("0e1").floating == 0);
2329 assert(parseJSON("0e1", JSONOptions.strictParsing).floating == 0);
2331 // Trailing characters after JSON value
2332 assert(parseJSON(`""asdf`).str == "");
2333 assertThrown!JSONException(parseJSON(`""asdf`, JSONOptions.strictParsing));
2334 assert(parseJSON("987\0").integer == 987);
2335 assertThrown!JSONException(parseJSON("987\0", JSONOptions.strictParsing));
2336 assert(parseJSON("987\0\0").integer == 987);
2337 assertThrown!JSONException(parseJSON("987\0\0", JSONOptions.strictParsing));
2338 assert(parseJSON("[]]").type == JSONType.array);
2339 assertThrown!JSONException(parseJSON("[]]", JSONOptions.strictParsing));
2340 assert(parseJSON("123 \t\r\n").integer == 123); // Trailing whitespace is OK
2341 assert(parseJSON("123 \t\r\n", JSONOptions.strictParsing).integer == 123);
2344 @system unittest
2346 import std.algorithm.iteration : map;
2347 import std.array : array;
2348 import std.exception : assertThrown;
2350 string s = `{ "a" : [1,2,3,], }`;
2351 JSONValue j = parseJSON(s);
2352 assert(j["a"].array().map!(i => i.integer()).array == [1,2,3]);
2354 assertThrown(parseJSON(s, -1, JSONOptions.strictParsing));
2357 @system unittest
2359 import std.algorithm.iteration : map;
2360 import std.array : array;
2361 import std.exception : assertThrown;
2363 string s = `{ "a" : { } , }`;
2364 JSONValue j = parseJSON(s);
2365 assert("a" in j);
2366 auto t = j["a"].object();
2367 assert(t.empty);
2369 assertThrown(parseJSON(s, -1, JSONOptions.strictParsing));
2372 // https://issues.dlang.org/show_bug.cgi?id=20330
2373 @safe unittest
2375 import std.array : appender;
2377 string s = `{"a":[1,2,3]}`;
2378 JSONValue j = parseJSON(s);
2380 auto app = appender!string();
2381 j.toString(app);
2383 assert(app.data == s, app.data);
2386 // https://issues.dlang.org/show_bug.cgi?id=20330
2387 @safe unittest
2389 import std.array : appender;
2390 import std.format.write : formattedWrite;
2392 string s =
2394 "a": [
2400 JSONValue j = parseJSON(s);
2402 auto app = appender!string();
2403 j.toPrettyString(app);
2405 assert(app.data == s, app.data);