1 // Written in the D programming language.
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)
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);
40 if (const(JSONValue
)* code
= "code" in j
)
42 if (code
.type() == JSONType
.integer
)
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
);
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
72 Flags that control how json is encoded and parsed.
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
88 /// Indicates the type of a `JSONValue`.
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
;
120 import std
.exception
: enforce
;
128 JSONValue
[string
] object
;
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
144 string s
= "{ \"language\": \"D\" }";
145 JSONValue j
= parseJSON(s
);
146 assert(j
.type
== JSONType
.object
);
147 assert(j
["language"].type
== JSONType
.string
);
151 * Value getter/setter for `JSONType.string`.
152 * Throws: `JSONException` for read access if `type` is not
155 @property string
str() const pure @trusted return scope
157 enforce
!JSONException(type
== JSONType
.string
,
158 "JSONValue is not a string");
162 @property string
str(return scope string v
) pure nothrow @nogc @trusted return // TODO make @safe
170 JSONValue j
= [ "language": "D" ];
173 assert(j
["language"].str == "D");
175 // change existing key to new string
176 j
["language"].str = "Perl";
177 assert(j
["language"].str == "Perl");
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
;
192 @property long integer(long v
) pure nothrow @safe @nogc
195 return store
.integer
;
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
;
210 @property ulong uinteger(ulong v
) pure nothrow @safe @nogc
213 return store
.uinteger
;
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
222 @property double floating() const pure @safe
224 enforce
!JSONException(type
== JSONType
.float_
,
225 "JSONValue is not a floating type");
226 return store
.floating
;
229 @property double floating(double v
) pure nothrow @safe @nogc
232 return store
.floating
;
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");
248 @property bool boolean(bool v
) pure nothrow @safe @nogc
257 assert(j
.boolean
== true);
260 assert(j
.boolean
== false);
263 import std
.exception
: assertThrown
;
264 assertThrown
!JSONException(j
.boolean
);
268 * Value getter/setter for `JSONType.object`.
269 * Throws: `JSONException` for read access if `type` is not
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");
285 @property JSONValue
[string
] object(return scope JSONValue
[string
] v
) pure nothrow @nogc @trusted // TODO make @safe
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:
298 * json.object = null;
299 * json.objectNoRef["hello"] = JSONValue("world");
300 * assert("hello" !in json.object);
303 * Throws: `JSONException` for read access if `type` is not
306 @property inout(JSONValue
[string
]) objectNoRef() inout pure @trusted
308 enforce
!JSONException(type
== JSONType
.object
,
309 "JSONValue is not an object");
314 * Value getter/setter for `JSONType.array`.
315 * Throws: `JSONException` for read access if `type` is not
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");
331 @property JSONValue
[] array(return scope JSONValue
[] v
) pure nothrow @nogc @trusted scope // TODO make @safe
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
345 * json.array = [JSONValue("hello")];
346 * json.arrayNoRef ~= JSONValue("world");
347 * assert(json.array.length == 1);
350 * Throws: `JSONException` for read access if `type` is not
353 @property inout(JSONValue
[]) arrayNoRef() inout pure @trusted
355 enforce
!JSONException(type
== JSONType
.array
,
356 "JSONValue is not an array");
360 /// Test whether the type is `JSONType.null_`
361 @property bool isNull() const pure nothrow @safe @nogc
363 return type
== JSONType
.null_
;
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
))
379 else static if (is(immutable T
== immutable bool))
383 else static if (isFloatingPoint
!T
)
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
;
394 throw new JSONException("JSONValue is not a number type");
397 else static if (isIntegral
!T
)
401 case JSONType
.uinteger
:
402 return uinteger
.to
!T
;
403 case JSONType
.integer
:
406 throw new JSONException("JSONValue is not a an integral type");
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
420 @property inout(T
) get(T
: JSONValue
[string
])() inout pure @trusted
427 import std
.exception
;
438 "h": ` ~ ulong.max
.to
!string
~ `,
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
;
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'
482 import std
.utf
: byUTF
;
483 store
.str = cast(immutable)(arg
.byUTF
!char.array
);
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
;
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
; }();
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
))
528 () @trusted { store
.array
= t
; }();
532 JSONValue
[] new_arg
= new JSONValue
[arg
.length
];
534 new_arg
[i
] = JSONValue(e
);
535 () @trusted { store
.array
= new_arg
; }();
538 else static if (is(T
: JSONValue
))
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
))
558 JSONValue
[] new_arg
= new JSONValue
[arg
.length
];
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
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
)
582 this(T
)(ref T arg
) if (isStaticArray
!T
)
587 this(T
: JSONValue
)(inout T arg
) inout
595 JSONValue j
= JSONValue( "a string" );
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
))
610 void opAssign(T
)(ref T arg
) if (isStaticArray
!T
)
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");
629 JSONValue j
= JSONValue( [42, 43, 44] );
630 assert( j
[0].integer
== 42 );
631 assert( j
[1].integer
== 43 );
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
);
647 JSONValue j
= JSONValue( ["language": "D"] );
648 assert( j
["language"].str == "D" );
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
;
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");
692 JSONValue j
= JSONValue( ["Perl", "C"] );
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
);
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
))
727 static assert(false, "argument is not an array or a JSONValue array");
733 * Support for the `in` operator.
735 * Tests wether a key can be found in an object.
738 * when found, the `inout(JSONValue)*` that matches to the key,
741 * Throws: `JSONException` if the right hand side argument `JSONType`
744 inout(JSONValue
)* opBinaryRight(string op
: "in")(string k
) inout @safe
746 return k
in this.objectNoRef
;
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
;
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
;
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
;
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
;
811 return type_tag
== rhs
.type_tag
&& store
.array
== rhs
.store
.array
;
813 case JSONType
.false_
:
815 return type_tag
== rhs
.type_tag
;
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
832 foreach (size_t index
, ref value
; array
)
834 result
= dg(index
, value
);
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");
849 foreach (string key
, ref value
; object
)
851 result
= dg(key
, value
);
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
);
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
896 static struct MyCustomType
898 public string
toString () const @system { return null; }
904 public JSONValue
asJSON() const @system { return JSONValue
.init
; }
908 if (false) // Just checking attributes
911 MyCustomType ilovedlang
;
913 json
["foo"] = ilovedlang
;
914 auto s
= ilovedlang
in json
;
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.
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
;
937 root
.type_tag
= JSONType
.null_
;
939 // Avoid UTF decoding when possible, as it is unnecessary when
941 static if (is(T
: const(char)[]))
944 alias Char
= Unqual
!(ElementType
!T
);
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
);
960 error("Empty JSON body");
965 bool isWhite(dchar c
)
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
);
979 if (json
.empty
) error("Unexpected end of data.");
980 static if (is(T
: const(char)[]))
1008 if (json
.empty
) return '\0';
1014 Nullable
!Char
peekCharNullable()
1016 if (next
.isNull
&& !json
.empty
)
1023 void skipWhitespace()
1027 auto c
= peekCharNullable();
1037 Char
getChar(bool SkipWhitespace
= false)()
1039 static if (SkipWhitespace
) skipWhitespace();
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;
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
);
1086 string
parseString()
1088 import std
.uni
: isSurrogateHi
, isSurrogateLo
;
1089 import std
.utf
: encode
, decode
;
1091 auto str = appender
!string();
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;
1114 wchar wc
= parseWChar();
1116 // Non-BMP characters are escaped as a pair of
1117 // UTF-16 surrogate characters (see RFC 4627).
1118 if (isSurrogateHi(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();
1126 val
= decode(pair
[], index
);
1127 if (index
!= 2) error("Invalid escaped surrogate pair");
1130 if (isSurrogateLo(wc
))
1131 error(text("Unexpected low surrogate"));
1136 immutable len
= encode
!(Yes
.useReplacementDchar
)(buf
, val
);
1137 str.put(buf
[0 .. len
]);
1141 error(text("Invalid escape sequence '\\", c
, "'."));
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.
1153 if (c
< 0x20 && (strict || c
!= 0))
1154 error("Illegal control character.");
1159 return str.data
.length ?
str.data
: "";
1162 bool tryGetSpecialFloat(string
str, out double val
) {
1165 case JSONFloatLiteral
.nan
:
1168 case JSONFloatLiteral
.inf
:
1169 val
= double.infinity
;
1171 case JSONFloatLiteral
.negativeInf
:
1172 val
= -double.infinity
;
1179 void parseValue(ref JSONValue value
)
1183 if (maxDepth
!= -1 && depth
> maxDepth
) error("Nesting too deep.");
1185 auto c
= getChar
!true();
1192 value
.object
= null;
1196 JSONValue
[string
] obj
;
1200 if (!strict
&& peekChar() == '}')
1205 string name
= parseString();
1211 while (testChar(','));
1220 value
.type_tag
= JSONType
.array
;
1228 if (!strict
&& peekChar() == ']')
1233 parseValue(element
);
1236 while (testChar(','));
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_
;
1257 case '0': .. case '9':
1259 auto number
= appender
!string();
1260 bool isFloat
, isNegative
;
1264 if (!isDigit(c
)) error("Digit expected");
1266 Next
: number
.put(c
);
1268 if (isDigit(peekChar()))
1282 if (strict
&& c
== '0')
1285 if (isDigit(peekChar()))
1287 error("Additional digits not allowed after initial zero digit");
1302 if (testChar
!(false, false)('e'))
1306 if (testChar('+')) number
.put('+');
1307 else if (testChar('-')) number
.put('-');
1312 string data
= number
.data
;
1315 value
.type_tag
= JSONType
.float_
;
1316 value
.store
.floating
= parse
!double(data
);
1322 value
.store
.integer
= parse
!long(data
);
1323 value
.type_tag
= JSONType
.integer
;
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
;
1336 value
.store
.integer
= u
;
1337 value
.type_tag
= JSONType
.integer
;
1344 if (strict
) goto default;
1347 value
.type_tag
= JSONType
.true_
;
1348 checkChar
!false('r', strict
);
1349 checkChar
!false('u', strict
);
1350 checkChar
!false('e', strict
);
1354 if (strict
) goto default;
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
);
1365 if (strict
) goto default;
1368 value
.type_tag
= JSONType
.null_
;
1369 checkChar
!false('u', strict
);
1370 checkChar
!false('l', strict
);
1371 checkChar
!false('l', strict
);
1375 error(text("Unexpected character '", c
, "'."));
1385 if (!peekCharNullable().isNull
) error("Trailing non-whitespace characters");
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
);
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}}`);
1409 // Ensure we can parse JSON from a @system range.
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
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.
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
);
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)
1473 foreach (Char c
; str)
1477 case '"': json
.put("\\\""); break;
1478 case '\\': json
.put("\\\\"); break;
1481 if (!(options
& JSONOptions
.doNotEscapeSlashes
))
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;
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
])
1511 foreach_reverse (i
; 0 .. 4)
1513 char ch
= (wc
>>> (4 * i
)) & 0x0f;
1514 ch
+= ch
< 10 ?
'0' : 'A' - 10;
1530 void toString(string
str)
1532 // Avoid UTF decoding when possible, as it is unnecessary when
1534 if (options
& JSONOptions
.escapeNonAsciiChars
)
1535 toStringImpl
!dchar(str);
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
;
1549 void delegate(ref const JSONValue
, ulong) @system toValue
;
1552 void toValueImpl(ref const JSONValue value
, ulong indentLevel
)
1554 void putTabs(ulong additionalIndent
= 0)
1557 foreach (i
; 0 .. indentLevel
+ additionalIndent
)
1565 void putCharAndEOL(char ch
)
1571 final switch (value
.type
)
1573 case JSONType
.object
:
1574 auto obj
= value
.objectNoRef
;
1584 void emit(R
)(R names
)
1586 foreach (name
; names
)
1588 auto member
= obj
[name
];
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
];
1620 case JSONType
.array
:
1621 auto arr
= value
.arrayNoRef
;
1629 foreach (i
, el
; arr
)
1634 toValue(el
, indentLevel
+ 1);
1642 case JSONType
.string
:
1643 toString(value
.str);
1646 case JSONType
.integer
:
1647 json
.put(to
!string(value
.store
.integer
));
1650 case JSONType
.uinteger
:
1651 json
.put(to
!string(value
.store
.uinteger
));
1654 case JSONType
.float_
:
1655 import std
.math
.traits
: isNaN
, isInfinity
;
1657 auto val
= value
.store
.floating
;
1661 if (options
& JSONOptions
.specialFloatLiterals
)
1663 toString(JSONFloatLiteral
.nan
);
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
);
1679 throw new JSONException(
1680 "Cannot encode Infinity. Consider passing the specialFloatLiterals flag.");
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)
1692 auto result
= buf
[].sformat
!"%.18g"(val
);
1694 if (!result
.canFind('e') && !result
.canFind('.'))
1699 case JSONType
.true_
:
1703 case JSONType
.false_
:
1707 case JSONType
.null_
:
1713 toValue
= &toValueImpl
;
1718 // https://issues.dlang.org/show_bug.cgi?id=12897
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
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
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 }";
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_
);
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
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
1806 super(text(msg
, " (Line ", line
, ":", pos
, ")"));
1811 this(string msg
, string file
, size_t line
) pure nothrow @safe
1813 super(msg
, file
, line
);
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]);
1833 assert(jv
.type
== JSONType
.integer
);
1834 assertNotThrown(jv
.integer
);
1837 assert(jv
.type
== JSONType
.uinteger
);
1838 assertNotThrown(jv
.uinteger
);
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"]);
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");
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));
1877 assert(jv
.type
== JSONType
.null_
);
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
1899 assert(jv
.type
== JSONType
.integer
);
1902 assert(jv
.type
== JSONType
.string
);
1903 assert(jv
.str == "123");
1906 assert(jv
.type
== JSONType
.integer
);
1907 assert(jv
.integer
== 1);
1910 assert(jv
.type
== JSONType
.uinteger
);
1911 assert(jv
.uinteger
== 2u);
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)]);
1926 assert(jv
.type
== JSONType
.true_
);
1929 assert(jv
.type
== JSONType
.false_
);
1933 assert(jv
.type
== JSONType
.true_
);
1936 @system pure unittest
1938 // Adding new json element via array() / object() directly
1940 JSONValue jarr
= JSONValue([10]);
1942 jarr
.array
~= JSONValue(i
);
1943 assert(jarr
.array
.length
== 10);
1945 JSONValue jobj
= JSONValue(["key" : JSONValue("value")]);
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]);
1957 jarr
~= [JSONValue(i
)];
1958 assert(jarr
.array
.length
== 10);
1960 JSONValue jobj
= JSONValue(["key" : JSONValue("value")]);
1962 jobj
[text("key", i
)] = JSONValue(text("value", i
));
1963 assert(jobj
.object
.length
== 10);
1966 auto jarr2
= jarr
~ [1,2,3];
1968 assert(jarr
[0] == JSONValue(10));
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.
1991 `"\"\\\/\b\f\n\r\t"`,
1993 `[12,"foo",true,false]`,
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`;
2002 jsons
~= dbl1_844
~ `e+019`;
2004 jsons
~= dbl1_844
~ `e+19`;
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
) == "\"\<\>\"");
2027 assert(val
.to
!string() == "\"\<\>\"");
2028 val
= parseJSON(`"\u0391\u0392\u0393"`);
2029 assert(toJSON(val
) == "\"\Α\Β\Γ\"");
2030 assert(val
.to
!string() == "\"\Α\Β\Γ\"");
2031 val
= parseJSON(`"\u2660\u2666"`);
2032 assert(toJSON(val
) == "\"\♠\♦\"");
2033 assert(val
.to
!string() == "\"\♠\♦\"");
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
);
2046 val
= parseJSON(`{"a":[null,{"x":1},{},[]]}`);
2047 assert(toJSON(val
, true) == `{
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
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");
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);
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)
2117 import std
.exception
: assertThrown
;
2118 import std
.math
.traits
: isNaN
, isInfinity
;
2120 // expected representations of NaN and Inf
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
2163 testVal
= (JSONValue
[string
]).init
;
2164 testVal
= JSONValue
[].init
;
2166 assert(testVal
.isNull
);
2169 // https://issues.dlang.org/show_bug.cgi?id=15884
2170 pure nothrow @safe unittest
2172 import std
.typecons
;
2175 JSONValue testVal
= a
;
2176 assert(testVal
.type
== JSONType
.string
);
2178 assert(testVal
.type
== JSONType
.string
);
2185 // https://issues.dlang.org/show_bug.cgi?id=15885
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);
2198 return num1
== num0
;
2201 assert(test( 0.23));
2202 assert(test(-0.23));
2203 assert(test(1.223e+24));
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));
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
2223 import std
.exception
: assertThrown
;
2225 assertThrown
!JSONException(parseJSON("\"a\nb\""));
2228 // https://issues.dlang.org/show_bug.cgi?id=17556
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
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
2247 assert(parseJSON("\"\xFF\"").str == "\xFF");
2248 assert(parseJSON("\"\U0001D11E\"").str == "\U0001D11E");
2251 // https://issues.dlang.org/show_bug.cgi?id=17553
2254 auto v
= JSONValue("\xFF");
2255 assert(toJSON(v
) == "\"\xFF\"");
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)
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)
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
);
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);
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
));
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
);
2366 auto t
= j
["a"].object();
2369 assertThrown(parseJSON(s
, -1, JSONOptions
.strictParsing
));
2372 // https://issues.dlang.org/show_bug.cgi?id=20330
2375 import std
.array
: appender
;
2377 string s
= `{"a":[1,2,3]}`;
2378 JSONValue j
= parseJSON(s
);
2380 auto app
= appender
!string();
2383 assert(app
.data
== s
, app
.data
);
2386 // https://issues.dlang.org/show_bug.cgi?id=20330
2389 import std
.array
: appender
;
2390 import std
.format
.write
: formattedWrite
;
2400 JSONValue j
= parseJSON(s
);
2402 auto app
= appender
!string();
2403 j
.toPrettyString(app
);
2405 assert(app
.data
== s
, app
.data
);