]> git.ipfire.org Git - thirdparty/gcc.git/blob - libphobos/src/std/json.d
Add D front-end, libphobos library, and D2 testsuite.
[thirdparty/gcc.git] / libphobos / src / std / json.d
1 // Written in the D programming language.
2
3 /**
4 JavaScript Object Notation
5
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/)
10 Source: $(PHOBOSSRC std/_json.d)
11 */
12 /*
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)
17 */
18 module std.json;
19
20 import std.array;
21 import std.conv;
22 import std.range.primitives;
23 import std.traits;
24
25 ///
26 @system unittest
27 {
28 import std.conv : to;
29
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);
37
38 // check a type
39 long x;
40 if (const(JSONValue)* code = "code" in j)
41 {
42 if (code.type() == JSON_TYPE.INTEGER)
43 x = code.integer;
44 else
45 x = to!int(code.str);
46 }
47
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");
56
57 string jjStr = `{"language":"D","list":["a","b","c","D"],"rating":3.5}`;
58 assert(jj.toString == jjStr);
59 }
60
61 /**
62 String literals used to represent special float values within JSON strings.
63 */
64 enum JSONFloatLiteral : string
65 {
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
69 }
70
71 /**
72 Flags that control how json is encoded and parsed.
73 */
74 enum JSONOptions
75 {
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 }
81
82 /**
83 JSON type enumeration
84 */
85 enum JSON_TYPE : byte
86 {
87 /// Indicates the type of a $(D JSONValue).
88 NULL,
89 STRING, /// ditto
90 INTEGER, /// ditto
91 UINTEGER,/// ditto
92 FLOAT, /// ditto
93 OBJECT, /// ditto
94 ARRAY, /// ditto
95 TRUE, /// ditto
96 FALSE /// ditto
97 }
98
99 /**
100 JSON value node
101 */
102 struct JSONValue
103 {
104 import std.exception : enforceEx, enforce;
105
106 union Store
107 {
108 string str;
109 long integer;
110 ulong uinteger;
111 double floating;
112 JSONValue[string] object;
113 JSONValue[] array;
114 }
115 private Store store;
116 private JSON_TYPE type_tag;
117
118 /**
119 Returns the JSON_TYPE of the value stored in this structure.
120 */
121 @property JSON_TYPE type() const pure nothrow @safe @nogc
122 {
123 return type_tag;
124 }
125 ///
126 @safe unittest
127 {
128 string s = "{ \"language\": \"D\" }";
129 JSONValue j = parseJSON(s);
130 assert(j.type == JSON_TYPE.OBJECT);
131 assert(j["language"].type == JSON_TYPE.STRING);
132 }
133
134 /***
135 * Value getter/setter for $(D JSON_TYPE.STRING).
136 * Throws: $(D JSONException) for read access if $(D type) is not
137 * $(D JSON_TYPE.STRING).
138 */
139 @property string str() const pure @trusted
140 {
141 enforce!JSONException(type == JSON_TYPE.STRING,
142 "JSONValue is not a string");
143 return store.str;
144 }
145 /// ditto
146 @property string str(string v) pure nothrow @nogc @safe
147 {
148 assign(v);
149 return v;
150 }
151 ///
152 @safe unittest
153 {
154 JSONValue j = [ "language": "D" ];
155
156 // get value
157 assert(j["language"].str == "D");
158
159 // change existing key to new string
160 j["language"].str = "Perl";
161 assert(j["language"].str == "Perl");
162 }
163
164 /***
165 * Value getter/setter for $(D JSON_TYPE.INTEGER).
166 * Throws: $(D JSONException) for read access if $(D type) is not
167 * $(D JSON_TYPE.INTEGER).
168 */
169 @property inout(long) integer() inout pure @safe
170 {
171 enforce!JSONException(type == JSON_TYPE.INTEGER,
172 "JSONValue is not an integer");
173 return store.integer;
174 }
175 /// ditto
176 @property long integer(long v) pure nothrow @safe @nogc
177 {
178 assign(v);
179 return store.integer;
180 }
181
182 /***
183 * Value getter/setter for $(D JSON_TYPE.UINTEGER).
184 * Throws: $(D JSONException) for read access if $(D type) is not
185 * $(D JSON_TYPE.UINTEGER).
186 */
187 @property inout(ulong) uinteger() inout pure @safe
188 {
189 enforce!JSONException(type == JSON_TYPE.UINTEGER,
190 "JSONValue is not an unsigned integer");
191 return store.uinteger;
192 }
193 /// ditto
194 @property ulong uinteger(ulong v) pure nothrow @safe @nogc
195 {
196 assign(v);
197 return store.uinteger;
198 }
199
200 /***
201 * Value getter/setter for $(D JSON_TYPE.FLOAT). Note that despite
202 * the name, this is a $(B 64)-bit `double`, not a 32-bit `float`.
203 * Throws: $(D JSONException) for read access if $(D type) is not
204 * $(D JSON_TYPE.FLOAT).
205 */
206 @property inout(double) floating() inout pure @safe
207 {
208 enforce!JSONException(type == JSON_TYPE.FLOAT,
209 "JSONValue is not a floating type");
210 return store.floating;
211 }
212 /// ditto
213 @property double floating(double v) pure nothrow @safe @nogc
214 {
215 assign(v);
216 return store.floating;
217 }
218
219 /***
220 * Value getter/setter for $(D JSON_TYPE.OBJECT).
221 * Throws: $(D JSONException) for read access if $(D type) is not
222 * $(D JSON_TYPE.OBJECT).
223 * Note: this is @system because of the following pattern:
224 ---
225 auto a = &(json.object());
226 json.uinteger = 0; // overwrite AA pointer
227 (*a)["hello"] = "world"; // segmentation fault
228 ---
229 */
230 @property ref inout(JSONValue[string]) object() inout pure @system
231 {
232 enforce!JSONException(type == JSON_TYPE.OBJECT,
233 "JSONValue is not an object");
234 return store.object;
235 }
236 /// ditto
237 @property JSONValue[string] object(JSONValue[string] v) pure nothrow @nogc @safe
238 {
239 assign(v);
240 return v;
241 }
242
243 /***
244 * Value getter for $(D JSON_TYPE.OBJECT).
245 * Unlike $(D object), this retrieves the object by value and can be used in @safe code.
246 *
247 * A caveat is that, if the returned value is null, modifications will not be visible:
248 * ---
249 * JSONValue json;
250 * json.object = null;
251 * json.objectNoRef["hello"] = JSONValue("world");
252 * assert("hello" !in json.object);
253 * ---
254 *
255 * Throws: $(D JSONException) for read access if $(D type) is not
256 * $(D JSON_TYPE.OBJECT).
257 */
258 @property inout(JSONValue[string]) objectNoRef() inout pure @trusted
259 {
260 enforce!JSONException(type == JSON_TYPE.OBJECT,
261 "JSONValue is not an object");
262 return store.object;
263 }
264
265 /***
266 * Value getter/setter for $(D JSON_TYPE.ARRAY).
267 * Throws: $(D JSONException) for read access if $(D type) is not
268 * $(D JSON_TYPE.ARRAY).
269 * Note: this is @system because of the following pattern:
270 ---
271 auto a = &(json.array());
272 json.uinteger = 0; // overwrite array pointer
273 (*a)[0] = "world"; // segmentation fault
274 ---
275 */
276 @property ref inout(JSONValue[]) array() inout pure @system
277 {
278 enforce!JSONException(type == JSON_TYPE.ARRAY,
279 "JSONValue is not an array");
280 return store.array;
281 }
282 /// ditto
283 @property JSONValue[] array(JSONValue[] v) pure nothrow @nogc @safe
284 {
285 assign(v);
286 return v;
287 }
288
289 /***
290 * Value getter for $(D JSON_TYPE.ARRAY).
291 * Unlike $(D array), this retrieves the array by value and can be used in @safe code.
292 *
293 * A caveat is that, if you append to the returned array, the new values aren't visible in the
294 * JSONValue:
295 * ---
296 * JSONValue json;
297 * json.array = [JSONValue("hello")];
298 * json.arrayNoRef ~= JSONValue("world");
299 * assert(json.array.length == 1);
300 * ---
301 *
302 * Throws: $(D JSONException) for read access if $(D type) is not
303 * $(D JSON_TYPE.ARRAY).
304 */
305 @property inout(JSONValue[]) arrayNoRef() inout pure @trusted
306 {
307 enforce!JSONException(type == JSON_TYPE.ARRAY,
308 "JSONValue is not an array");
309 return store.array;
310 }
311
312 /// Test whether the type is $(D JSON_TYPE.NULL)
313 @property bool isNull() const pure nothrow @safe @nogc
314 {
315 return type == JSON_TYPE.NULL;
316 }
317
318 private void assign(T)(T arg) @safe
319 {
320 static if (is(T : typeof(null)))
321 {
322 type_tag = JSON_TYPE.NULL;
323 }
324 else static if (is(T : string))
325 {
326 type_tag = JSON_TYPE.STRING;
327 string t = arg;
328 () @trusted { store.str = t; }();
329 }
330 else static if (isSomeString!T) // issue 15884
331 {
332 type_tag = JSON_TYPE.STRING;
333 // FIXME: std.array.array(Range) is not deduced as 'pure'
334 () @trusted {
335 import std.utf : byUTF;
336 store.str = cast(immutable)(arg.byUTF!char.array);
337 }();
338 }
339 else static if (is(T : bool))
340 {
341 type_tag = arg ? JSON_TYPE.TRUE : JSON_TYPE.FALSE;
342 }
343 else static if (is(T : ulong) && isUnsigned!T)
344 {
345 type_tag = JSON_TYPE.UINTEGER;
346 store.uinteger = arg;
347 }
348 else static if (is(T : long))
349 {
350 type_tag = JSON_TYPE.INTEGER;
351 store.integer = arg;
352 }
353 else static if (isFloatingPoint!T)
354 {
355 type_tag = JSON_TYPE.FLOAT;
356 store.floating = arg;
357 }
358 else static if (is(T : Value[Key], Key, Value))
359 {
360 static assert(is(Key : string), "AA key must be string");
361 type_tag = JSON_TYPE.OBJECT;
362 static if (is(Value : JSONValue))
363 {
364 JSONValue[string] t = arg;
365 () @trusted { store.object = t; }();
366 }
367 else
368 {
369 JSONValue[string] aa;
370 foreach (key, value; arg)
371 aa[key] = JSONValue(value);
372 () @trusted { store.object = aa; }();
373 }
374 }
375 else static if (isArray!T)
376 {
377 type_tag = JSON_TYPE.ARRAY;
378 static if (is(ElementEncodingType!T : JSONValue))
379 {
380 JSONValue[] t = arg;
381 () @trusted { store.array = t; }();
382 }
383 else
384 {
385 JSONValue[] new_arg = new JSONValue[arg.length];
386 foreach (i, e; arg)
387 new_arg[i] = JSONValue(e);
388 () @trusted { store.array = new_arg; }();
389 }
390 }
391 else static if (is(T : JSONValue))
392 {
393 type_tag = arg.type;
394 store = arg.store;
395 }
396 else
397 {
398 static assert(false, text(`unable to convert type "`, T.stringof, `" to json`));
399 }
400 }
401
402 private void assignRef(T)(ref T arg) if (isStaticArray!T)
403 {
404 type_tag = JSON_TYPE.ARRAY;
405 static if (is(ElementEncodingType!T : JSONValue))
406 {
407 store.array = arg;
408 }
409 else
410 {
411 JSONValue[] new_arg = new JSONValue[arg.length];
412 foreach (i, e; arg)
413 new_arg[i] = JSONValue(e);
414 store.array = new_arg;
415 }
416 }
417
418 /**
419 * Constructor for $(D JSONValue). If $(D arg) is a $(D JSONValue)
420 * its value and type will be copied to the new $(D JSONValue).
421 * Note that this is a shallow copy: if type is $(D JSON_TYPE.OBJECT)
422 * or $(D JSON_TYPE.ARRAY) then only the reference to the data will
423 * be copied.
424 * Otherwise, $(D arg) must be implicitly convertible to one of the
425 * following types: $(D typeof(null)), $(D string), $(D ulong),
426 * $(D long), $(D double), an associative array $(D V[K]) for any $(D V)
427 * and $(D K) i.e. a JSON object, any array or $(D bool). The type will
428 * be set accordingly.
429 */
430 this(T)(T arg) if (!isStaticArray!T)
431 {
432 assign(arg);
433 }
434 /// Ditto
435 this(T)(ref T arg) if (isStaticArray!T)
436 {
437 assignRef(arg);
438 }
439 /// Ditto
440 this(T : JSONValue)(inout T arg) inout
441 {
442 store = arg.store;
443 type_tag = arg.type;
444 }
445 ///
446 @safe unittest
447 {
448 JSONValue j = JSONValue( "a string" );
449 j = JSONValue(42);
450
451 j = JSONValue( [1, 2, 3] );
452 assert(j.type == JSON_TYPE.ARRAY);
453
454 j = JSONValue( ["language": "D"] );
455 assert(j.type == JSON_TYPE.OBJECT);
456 }
457
458 void opAssign(T)(T arg) if (!isStaticArray!T && !is(T : JSONValue))
459 {
460 assign(arg);
461 }
462
463 void opAssign(T)(ref T arg) if (isStaticArray!T)
464 {
465 assignRef(arg);
466 }
467
468 /***
469 * Array syntax for json arrays.
470 * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.ARRAY).
471 */
472 ref inout(JSONValue) opIndex(size_t i) inout pure @safe
473 {
474 auto a = this.arrayNoRef;
475 enforceEx!JSONException(i < a.length,
476 "JSONValue array index is out of range");
477 return a[i];
478 }
479 ///
480 @safe unittest
481 {
482 JSONValue j = JSONValue( [42, 43, 44] );
483 assert( j[0].integer == 42 );
484 assert( j[1].integer == 43 );
485 }
486
487 /***
488 * Hash syntax for json objects.
489 * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT).
490 */
491 ref inout(JSONValue) opIndex(string k) inout pure @safe
492 {
493 auto o = this.objectNoRef;
494 return *enforce!JSONException(k in o,
495 "Key not found: " ~ k);
496 }
497 ///
498 @safe unittest
499 {
500 JSONValue j = JSONValue( ["language": "D"] );
501 assert( j["language"].str == "D" );
502 }
503
504 /***
505 * Operator sets $(D value) for element of JSON object by $(D key).
506 *
507 * If JSON value is null, then operator initializes it with object and then
508 * sets $(D value) for it.
509 *
510 * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT)
511 * or $(D JSON_TYPE.NULL).
512 */
513 void opIndexAssign(T)(auto ref T value, string key) pure
514 {
515 enforceEx!JSONException(type == JSON_TYPE.OBJECT || type == JSON_TYPE.NULL,
516 "JSONValue must be object or null");
517 JSONValue[string] aa = null;
518 if (type == JSON_TYPE.OBJECT)
519 {
520 aa = this.objectNoRef;
521 }
522
523 aa[key] = value;
524 this.object = aa;
525 }
526 ///
527 @safe unittest
528 {
529 JSONValue j = JSONValue( ["language": "D"] );
530 j["language"].str = "Perl";
531 assert( j["language"].str == "Perl" );
532 }
533
534 void opIndexAssign(T)(T arg, size_t i) pure
535 {
536 auto a = this.arrayNoRef;
537 enforceEx!JSONException(i < a.length,
538 "JSONValue array index is out of range");
539 a[i] = arg;
540 this.array = a;
541 }
542 ///
543 @safe unittest
544 {
545 JSONValue j = JSONValue( ["Perl", "C"] );
546 j[1].str = "D";
547 assert( j[1].str == "D" );
548 }
549
550 JSONValue opBinary(string op : "~", T)(T arg) @safe
551 {
552 auto a = this.arrayNoRef;
553 static if (isArray!T)
554 {
555 return JSONValue(a ~ JSONValue(arg).arrayNoRef);
556 }
557 else static if (is(T : JSONValue))
558 {
559 return JSONValue(a ~ arg.arrayNoRef);
560 }
561 else
562 {
563 static assert(false, "argument is not an array or a JSONValue array");
564 }
565 }
566
567 void opOpAssign(string op : "~", T)(T arg) @safe
568 {
569 auto a = this.arrayNoRef;
570 static if (isArray!T)
571 {
572 a ~= JSONValue(arg).arrayNoRef;
573 }
574 else static if (is(T : JSONValue))
575 {
576 a ~= arg.arrayNoRef;
577 }
578 else
579 {
580 static assert(false, "argument is not an array or a JSONValue array");
581 }
582 this.array = a;
583 }
584
585 /**
586 * Support for the $(D in) operator.
587 *
588 * Tests wether a key can be found in an object.
589 *
590 * Returns:
591 * when found, the $(D const(JSONValue)*) that matches to the key,
592 * otherwise $(D null).
593 *
594 * Throws: $(D JSONException) if the right hand side argument $(D JSON_TYPE)
595 * is not $(D OBJECT).
596 */
597 auto opBinaryRight(string op : "in")(string k) const @safe
598 {
599 return k in this.objectNoRef;
600 }
601 ///
602 @safe unittest
603 {
604 JSONValue j = [ "language": "D", "author": "walter" ];
605 string a = ("author" in j).str;
606 }
607
608 bool opEquals(const JSONValue rhs) const @nogc nothrow pure @safe
609 {
610 return opEquals(rhs);
611 }
612
613 bool opEquals(ref const JSONValue rhs) const @nogc nothrow pure @trusted
614 {
615 // Default doesn't work well since store is a union. Compare only
616 // what should be in store.
617 // This is @trusted to remain nogc, nothrow, fast, and usable from @safe code.
618 if (type_tag != rhs.type_tag) return false;
619
620 final switch (type_tag)
621 {
622 case JSON_TYPE.STRING:
623 return store.str == rhs.store.str;
624 case JSON_TYPE.INTEGER:
625 return store.integer == rhs.store.integer;
626 case JSON_TYPE.UINTEGER:
627 return store.uinteger == rhs.store.uinteger;
628 case JSON_TYPE.FLOAT:
629 return store.floating == rhs.store.floating;
630 case JSON_TYPE.OBJECT:
631 return store.object == rhs.store.object;
632 case JSON_TYPE.ARRAY:
633 return store.array == rhs.store.array;
634 case JSON_TYPE.TRUE:
635 case JSON_TYPE.FALSE:
636 case JSON_TYPE.NULL:
637 return true;
638 }
639 }
640
641 /// Implements the foreach $(D opApply) interface for json arrays.
642 int opApply(scope int delegate(size_t index, ref JSONValue) dg) @system
643 {
644 int result;
645
646 foreach (size_t index, ref value; array)
647 {
648 result = dg(index, value);
649 if (result)
650 break;
651 }
652
653 return result;
654 }
655
656 /// Implements the foreach $(D opApply) interface for json objects.
657 int opApply(scope int delegate(string key, ref JSONValue) dg) @system
658 {
659 enforce!JSONException(type == JSON_TYPE.OBJECT,
660 "JSONValue is not an object");
661 int result;
662
663 foreach (string key, ref value; object)
664 {
665 result = dg(key, value);
666 if (result)
667 break;
668 }
669
670 return result;
671 }
672
673 /***
674 * Implicitly calls $(D toJSON) on this JSONValue.
675 *
676 * $(I options) can be used to tweak the conversion behavior.
677 */
678 string toString(in JSONOptions options = JSONOptions.none) const @safe
679 {
680 return toJSON(this, false, options);
681 }
682
683 /***
684 * Implicitly calls $(D toJSON) on this JSONValue, like $(D toString), but
685 * also passes $(I true) as $(I pretty) argument.
686 *
687 * $(I options) can be used to tweak the conversion behavior
688 */
689 string toPrettyString(in JSONOptions options = JSONOptions.none) const @safe
690 {
691 return toJSON(this, true, options);
692 }
693 }
694
695 /**
696 Parses a serialized string and returns a tree of JSON values.
697 Throws: $(LREF JSONException) if the depth exceeds the max depth.
698 Params:
699 json = json-formatted string to parse
700 maxDepth = maximum depth of nesting allowed, -1 disables depth checking
701 options = enable decoding string representations of NaN/Inf as float values
702 */
703 JSONValue parseJSON(T)(T json, int maxDepth = -1, JSONOptions options = JSONOptions.none)
704 if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T))
705 {
706 import std.ascii : isWhite, isDigit, isHexDigit, toUpper, toLower;
707 import std.typecons : Yes;
708 JSONValue root;
709 root.type_tag = JSON_TYPE.NULL;
710
711 // Avoid UTF decoding when possible, as it is unnecessary when
712 // processing JSON.
713 static if (is(T : const(char)[]))
714 alias Char = char;
715 else
716 alias Char = Unqual!(ElementType!T);
717
718 if (json.empty) return root;
719
720 int depth = -1;
721 Char next = 0;
722 int line = 1, pos = 0;
723
724 void error(string msg)
725 {
726 throw new JSONException(msg, line, pos);
727 }
728
729 Char popChar()
730 {
731 if (json.empty) error("Unexpected end of data.");
732 static if (is(T : const(char)[]))
733 {
734 Char c = json[0];
735 json = json[1..$];
736 }
737 else
738 {
739 Char c = json.front;
740 json.popFront();
741 }
742
743 if (c == '\n')
744 {
745 line++;
746 pos = 0;
747 }
748 else
749 {
750 pos++;
751 }
752
753 return c;
754 }
755
756 Char peekChar()
757 {
758 if (!next)
759 {
760 if (json.empty) return '\0';
761 next = popChar();
762 }
763 return next;
764 }
765
766 void skipWhitespace()
767 {
768 while (isWhite(peekChar())) next = 0;
769 }
770
771 Char getChar(bool SkipWhitespace = false)()
772 {
773 static if (SkipWhitespace) skipWhitespace();
774
775 Char c;
776 if (next)
777 {
778 c = next;
779 next = 0;
780 }
781 else
782 c = popChar();
783
784 return c;
785 }
786
787 void checkChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c)
788 {
789 static if (SkipWhitespace) skipWhitespace();
790 auto c2 = getChar();
791 static if (!CaseSensitive) c2 = toLower(c2);
792
793 if (c2 != c) error(text("Found '", c2, "' when expecting '", c, "'."));
794 }
795
796 bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c)
797 {
798 static if (SkipWhitespace) skipWhitespace();
799 auto c2 = peekChar();
800 static if (!CaseSensitive) c2 = toLower(c2);
801
802 if (c2 != c) return false;
803
804 getChar();
805 return true;
806 }
807
808 wchar parseWChar()
809 {
810 wchar val = 0;
811 foreach_reverse (i; 0 .. 4)
812 {
813 auto hex = toUpper(getChar());
814 if (!isHexDigit(hex)) error("Expecting hex character");
815 val += (isDigit(hex) ? hex - '0' : hex - ('A' - 10)) << (4 * i);
816 }
817 return val;
818 }
819
820 string parseString()
821 {
822 import std.ascii : isControl;
823 import std.uni : isSurrogateHi, isSurrogateLo;
824 import std.utf : encode, decode;
825
826 auto str = appender!string();
827
828 Next:
829 switch (peekChar())
830 {
831 case '"':
832 getChar();
833 break;
834
835 case '\\':
836 getChar();
837 auto c = getChar();
838 switch (c)
839 {
840 case '"': str.put('"'); break;
841 case '\\': str.put('\\'); break;
842 case '/': str.put('/'); break;
843 case 'b': str.put('\b'); break;
844 case 'f': str.put('\f'); break;
845 case 'n': str.put('\n'); break;
846 case 'r': str.put('\r'); break;
847 case 't': str.put('\t'); break;
848 case 'u':
849 wchar wc = parseWChar();
850 dchar val;
851 // Non-BMP characters are escaped as a pair of
852 // UTF-16 surrogate characters (see RFC 4627).
853 if (isSurrogateHi(wc))
854 {
855 wchar[2] pair;
856 pair[0] = wc;
857 if (getChar() != '\\') error("Expected escaped low surrogate after escaped high surrogate");
858 if (getChar() != 'u') error("Expected escaped low surrogate after escaped high surrogate");
859 pair[1] = parseWChar();
860 size_t index = 0;
861 val = decode(pair[], index);
862 if (index != 2) error("Invalid escaped surrogate pair");
863 }
864 else
865 if (isSurrogateLo(wc))
866 error(text("Unexpected low surrogate"));
867 else
868 val = wc;
869
870 char[4] buf;
871 immutable len = encode!(Yes.useReplacementDchar)(buf, val);
872 str.put(buf[0 .. len]);
873 break;
874
875 default:
876 error(text("Invalid escape sequence '\\", c, "'."));
877 }
878 goto Next;
879
880 default:
881 // RFC 7159 states that control characters U+0000 through
882 // U+001F must not appear unescaped in a JSON string.
883 auto c = getChar();
884 if (isControl(c))
885 error("Illegal control character.");
886 str.put(c);
887 goto Next;
888 }
889
890 return str.data.length ? str.data : "";
891 }
892
893 bool tryGetSpecialFloat(string str, out double val) {
894 switch (str)
895 {
896 case JSONFloatLiteral.nan:
897 val = double.nan;
898 return true;
899 case JSONFloatLiteral.inf:
900 val = double.infinity;
901 return true;
902 case JSONFloatLiteral.negativeInf:
903 val = -double.infinity;
904 return true;
905 default:
906 return false;
907 }
908 }
909
910 void parseValue(ref JSONValue value)
911 {
912 depth++;
913
914 if (maxDepth != -1 && depth > maxDepth) error("Nesting too deep.");
915
916 auto c = getChar!true();
917
918 switch (c)
919 {
920 case '{':
921 if (testChar('}'))
922 {
923 value.object = null;
924 break;
925 }
926
927 JSONValue[string] obj;
928 do
929 {
930 checkChar('"');
931 string name = parseString();
932 checkChar(':');
933 JSONValue member;
934 parseValue(member);
935 obj[name] = member;
936 }
937 while (testChar(','));
938 value.object = obj;
939
940 checkChar('}');
941 break;
942
943 case '[':
944 if (testChar(']'))
945 {
946 value.type_tag = JSON_TYPE.ARRAY;
947 break;
948 }
949
950 JSONValue[] arr;
951 do
952 {
953 JSONValue element;
954 parseValue(element);
955 arr ~= element;
956 }
957 while (testChar(','));
958
959 checkChar(']');
960 value.array = arr;
961 break;
962
963 case '"':
964 auto str = parseString();
965
966 // if special float parsing is enabled, check if string represents NaN/Inf
967 if ((options & JSONOptions.specialFloatLiterals) &&
968 tryGetSpecialFloat(str, value.store.floating))
969 {
970 // found a special float, its value was placed in value.store.floating
971 value.type_tag = JSON_TYPE.FLOAT;
972 break;
973 }
974
975 value.type_tag = JSON_TYPE.STRING;
976 value.store.str = str;
977 break;
978
979 case '0': .. case '9':
980 case '-':
981 auto number = appender!string();
982 bool isFloat, isNegative;
983
984 void readInteger()
985 {
986 if (!isDigit(c)) error("Digit expected");
987
988 Next: number.put(c);
989
990 if (isDigit(peekChar()))
991 {
992 c = getChar();
993 goto Next;
994 }
995 }
996
997 if (c == '-')
998 {
999 number.put('-');
1000 c = getChar();
1001 isNegative = true;
1002 }
1003
1004 readInteger();
1005
1006 if (testChar('.'))
1007 {
1008 isFloat = true;
1009 number.put('.');
1010 c = getChar();
1011 readInteger();
1012 }
1013 if (testChar!(false, false)('e'))
1014 {
1015 isFloat = true;
1016 number.put('e');
1017 if (testChar('+')) number.put('+');
1018 else if (testChar('-')) number.put('-');
1019 c = getChar();
1020 readInteger();
1021 }
1022
1023 string data = number.data;
1024 if (isFloat)
1025 {
1026 value.type_tag = JSON_TYPE.FLOAT;
1027 value.store.floating = parse!double(data);
1028 }
1029 else
1030 {
1031 if (isNegative)
1032 value.store.integer = parse!long(data);
1033 else
1034 value.store.uinteger = parse!ulong(data);
1035
1036 value.type_tag = !isNegative && value.store.uinteger & (1UL << 63) ?
1037 JSON_TYPE.UINTEGER : JSON_TYPE.INTEGER;
1038 }
1039 break;
1040
1041 case 't':
1042 case 'T':
1043 value.type_tag = JSON_TYPE.TRUE;
1044 checkChar!(false, false)('r');
1045 checkChar!(false, false)('u');
1046 checkChar!(false, false)('e');
1047 break;
1048
1049 case 'f':
1050 case 'F':
1051 value.type_tag = JSON_TYPE.FALSE;
1052 checkChar!(false, false)('a');
1053 checkChar!(false, false)('l');
1054 checkChar!(false, false)('s');
1055 checkChar!(false, false)('e');
1056 break;
1057
1058 case 'n':
1059 case 'N':
1060 value.type_tag = JSON_TYPE.NULL;
1061 checkChar!(false, false)('u');
1062 checkChar!(false, false)('l');
1063 checkChar!(false, false)('l');
1064 break;
1065
1066 default:
1067 error(text("Unexpected character '", c, "'."));
1068 }
1069
1070 depth--;
1071 }
1072
1073 parseValue(root);
1074 return root;
1075 }
1076
1077 @safe unittest
1078 {
1079 enum issue15742objectOfObject = `{ "key1": { "key2": 1 }}`;
1080 static assert(parseJSON(issue15742objectOfObject).type == JSON_TYPE.OBJECT);
1081
1082 enum issue15742arrayOfArray = `[[1]]`;
1083 static assert(parseJSON(issue15742arrayOfArray).type == JSON_TYPE.ARRAY);
1084 }
1085
1086 @safe unittest
1087 {
1088 // Ensure we can parse and use JSON from @safe code
1089 auto a = `{ "key1": { "key2": 1 }}`.parseJSON;
1090 assert(a["key1"]["key2"].integer == 1);
1091 assert(a.toString == `{"key1":{"key2":1}}`);
1092 }
1093
1094 @system unittest
1095 {
1096 // Ensure we can parse JSON from a @system range.
1097 struct Range
1098 {
1099 string s;
1100 size_t index;
1101 @system
1102 {
1103 bool empty() { return index >= s.length; }
1104 void popFront() { index++; }
1105 char front() { return s[index]; }
1106 }
1107 }
1108 auto s = Range(`{ "key1": { "key2": 1 }}`);
1109 auto json = parseJSON(s);
1110 assert(json["key1"]["key2"].integer == 1);
1111 }
1112
1113 /**
1114 Parses a serialized string and returns a tree of JSON values.
1115 Throws: $(REF JSONException, std,json) if the depth exceeds the max depth.
1116 Params:
1117 json = json-formatted string to parse
1118 options = enable decoding string representations of NaN/Inf as float values
1119 */
1120 JSONValue parseJSON(T)(T json, JSONOptions options)
1121 if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T))
1122 {
1123 return parseJSON!T(json, -1, options);
1124 }
1125
1126 deprecated(
1127 "Please use the overload that takes a ref JSONValue rather than a pointer. This overload will "
1128 ~ "be removed in November 2017.")
1129 string toJSON(in JSONValue* root, in bool pretty = false, in JSONOptions options = JSONOptions.none) @safe
1130 {
1131 return toJSON(*root, pretty, options);
1132 }
1133
1134 /**
1135 Takes a tree of JSON values and returns the serialized string.
1136
1137 Any Object types will be serialized in a key-sorted order.
1138
1139 If $(D pretty) is false no whitespaces are generated.
1140 If $(D pretty) is true serialized string is formatted to be human-readable.
1141 Set the $(LREF JSONOptions.specialFloatLiterals) flag is set in $(D options) to encode NaN/Infinity as strings.
1142 */
1143 string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions options = JSONOptions.none) @safe
1144 {
1145 auto json = appender!string();
1146
1147 void toStringImpl(Char)(string str) @safe
1148 {
1149 json.put('"');
1150
1151 foreach (Char c; str)
1152 {
1153 switch (c)
1154 {
1155 case '"': json.put("\\\""); break;
1156 case '\\': json.put("\\\\"); break;
1157
1158 case '/':
1159 if (!(options & JSONOptions.doNotEscapeSlashes))
1160 json.put('\\');
1161 json.put('/');
1162 break;
1163
1164 case '\b': json.put("\\b"); break;
1165 case '\f': json.put("\\f"); break;
1166 case '\n': json.put("\\n"); break;
1167 case '\r': json.put("\\r"); break;
1168 case '\t': json.put("\\t"); break;
1169 default:
1170 {
1171 import std.ascii : isControl;
1172 import std.utf : encode;
1173
1174 // Make sure we do UTF decoding iff we want to
1175 // escape Unicode characters.
1176 assert(((options & JSONOptions.escapeNonAsciiChars) != 0)
1177 == is(Char == dchar));
1178
1179 with (JSONOptions) if (isControl(c) ||
1180 ((options & escapeNonAsciiChars) >= escapeNonAsciiChars && c >= 0x80))
1181 {
1182 // Ensure non-BMP characters are encoded as a pair
1183 // of UTF-16 surrogate characters, as per RFC 4627.
1184 wchar[2] wchars; // 1 or 2 UTF-16 code units
1185 size_t wNum = encode(wchars, c); // number of UTF-16 code units
1186 foreach (wc; wchars[0 .. wNum])
1187 {
1188 json.put("\\u");
1189 foreach_reverse (i; 0 .. 4)
1190 {
1191 char ch = (wc >>> (4 * i)) & 0x0f;
1192 ch += ch < 10 ? '0' : 'A' - 10;
1193 json.put(ch);
1194 }
1195 }
1196 }
1197 else
1198 {
1199 json.put(c);
1200 }
1201 }
1202 }
1203 }
1204
1205 json.put('"');
1206 }
1207
1208 void toString(string str) @safe
1209 {
1210 // Avoid UTF decoding when possible, as it is unnecessary when
1211 // processing JSON.
1212 if (options & JSONOptions.escapeNonAsciiChars)
1213 toStringImpl!dchar(str);
1214 else
1215 toStringImpl!char(str);
1216 }
1217
1218 void toValue(ref in JSONValue value, ulong indentLevel) @safe
1219 {
1220 void putTabs(ulong additionalIndent = 0)
1221 {
1222 if (pretty)
1223 foreach (i; 0 .. indentLevel + additionalIndent)
1224 json.put(" ");
1225 }
1226 void putEOL()
1227 {
1228 if (pretty)
1229 json.put('\n');
1230 }
1231 void putCharAndEOL(char ch)
1232 {
1233 json.put(ch);
1234 putEOL();
1235 }
1236
1237 final switch (value.type)
1238 {
1239 case JSON_TYPE.OBJECT:
1240 auto obj = value.objectNoRef;
1241 if (!obj.length)
1242 {
1243 json.put("{}");
1244 }
1245 else
1246 {
1247 putCharAndEOL('{');
1248 bool first = true;
1249
1250 void emit(R)(R names)
1251 {
1252 foreach (name; names)
1253 {
1254 auto member = obj[name];
1255 if (!first)
1256 putCharAndEOL(',');
1257 first = false;
1258 putTabs(1);
1259 toString(name);
1260 json.put(':');
1261 if (pretty)
1262 json.put(' ');
1263 toValue(member, indentLevel + 1);
1264 }
1265 }
1266
1267 import std.algorithm.sorting : sort;
1268 // @@@BUG@@@ 14439
1269 // auto names = obj.keys; // aa.keys can't be called in @safe code
1270 auto names = new string[obj.length];
1271 size_t i = 0;
1272 foreach (k, v; obj)
1273 {
1274 names[i] = k;
1275 i++;
1276 }
1277 sort(names);
1278 emit(names);
1279
1280 putEOL();
1281 putTabs();
1282 json.put('}');
1283 }
1284 break;
1285
1286 case JSON_TYPE.ARRAY:
1287 auto arr = value.arrayNoRef;
1288 if (arr.empty)
1289 {
1290 json.put("[]");
1291 }
1292 else
1293 {
1294 putCharAndEOL('[');
1295 foreach (i, el; arr)
1296 {
1297 if (i)
1298 putCharAndEOL(',');
1299 putTabs(1);
1300 toValue(el, indentLevel + 1);
1301 }
1302 putEOL();
1303 putTabs();
1304 json.put(']');
1305 }
1306 break;
1307
1308 case JSON_TYPE.STRING:
1309 toString(value.str);
1310 break;
1311
1312 case JSON_TYPE.INTEGER:
1313 json.put(to!string(value.store.integer));
1314 break;
1315
1316 case JSON_TYPE.UINTEGER:
1317 json.put(to!string(value.store.uinteger));
1318 break;
1319
1320 case JSON_TYPE.FLOAT:
1321 import std.math : isNaN, isInfinity;
1322
1323 auto val = value.store.floating;
1324
1325 if (val.isNaN)
1326 {
1327 if (options & JSONOptions.specialFloatLiterals)
1328 {
1329 toString(JSONFloatLiteral.nan);
1330 }
1331 else
1332 {
1333 throw new JSONException(
1334 "Cannot encode NaN. Consider passing the specialFloatLiterals flag.");
1335 }
1336 }
1337 else if (val.isInfinity)
1338 {
1339 if (options & JSONOptions.specialFloatLiterals)
1340 {
1341 toString((val > 0) ? JSONFloatLiteral.inf : JSONFloatLiteral.negativeInf);
1342 }
1343 else
1344 {
1345 throw new JSONException(
1346 "Cannot encode Infinity. Consider passing the specialFloatLiterals flag.");
1347 }
1348 }
1349 else
1350 {
1351 import std.format : format;
1352 // The correct formula for the number of decimal digits needed for lossless round
1353 // trips is actually:
1354 // ceil(log(pow(2.0, double.mant_dig - 1)) / log(10.0) + 1) == (double.dig + 2)
1355 // Anything less will round off (1 + double.epsilon)
1356 json.put("%.18g".format(val));
1357 }
1358 break;
1359
1360 case JSON_TYPE.TRUE:
1361 json.put("true");
1362 break;
1363
1364 case JSON_TYPE.FALSE:
1365 json.put("false");
1366 break;
1367
1368 case JSON_TYPE.NULL:
1369 json.put("null");
1370 break;
1371 }
1372 }
1373
1374 toValue(root, 0);
1375 return json.data;
1376 }
1377
1378 @safe unittest // bugzilla 12897
1379 {
1380 JSONValue jv0 = JSONValue("test测试");
1381 assert(toJSON(jv0, false, JSONOptions.escapeNonAsciiChars) == `"test\u6D4B\u8BD5"`);
1382 JSONValue jv00 = JSONValue("test\u6D4B\u8BD5");
1383 assert(toJSON(jv00, false, JSONOptions.none) == `"test测试"`);
1384 assert(toJSON(jv0, false, JSONOptions.none) == `"test测试"`);
1385 JSONValue jv1 = JSONValue("été");
1386 assert(toJSON(jv1, false, JSONOptions.escapeNonAsciiChars) == `"\u00E9t\u00E9"`);
1387 JSONValue jv11 = JSONValue("\u00E9t\u00E9");
1388 assert(toJSON(jv11, false, JSONOptions.none) == `"été"`);
1389 assert(toJSON(jv1, false, JSONOptions.none) == `"été"`);
1390 }
1391
1392 /**
1393 Exception thrown on JSON errors
1394 */
1395 class JSONException : Exception
1396 {
1397 this(string msg, int line = 0, int pos = 0) pure nothrow @safe
1398 {
1399 if (line)
1400 super(text(msg, " (Line ", line, ":", pos, ")"));
1401 else
1402 super(msg);
1403 }
1404
1405 this(string msg, string file, size_t line) pure nothrow @safe
1406 {
1407 super(msg, file, line);
1408 }
1409 }
1410
1411
1412 @system unittest
1413 {
1414 import std.exception;
1415 JSONValue jv = "123";
1416 assert(jv.type == JSON_TYPE.STRING);
1417 assertNotThrown(jv.str);
1418 assertThrown!JSONException(jv.integer);
1419 assertThrown!JSONException(jv.uinteger);
1420 assertThrown!JSONException(jv.floating);
1421 assertThrown!JSONException(jv.object);
1422 assertThrown!JSONException(jv.array);
1423 assertThrown!JSONException(jv["aa"]);
1424 assertThrown!JSONException(jv[2]);
1425
1426 jv = -3;
1427 assert(jv.type == JSON_TYPE.INTEGER);
1428 assertNotThrown(jv.integer);
1429
1430 jv = cast(uint) 3;
1431 assert(jv.type == JSON_TYPE.UINTEGER);
1432 assertNotThrown(jv.uinteger);
1433
1434 jv = 3.0;
1435 assert(jv.type == JSON_TYPE.FLOAT);
1436 assertNotThrown(jv.floating);
1437
1438 jv = ["key" : "value"];
1439 assert(jv.type == JSON_TYPE.OBJECT);
1440 assertNotThrown(jv.object);
1441 assertNotThrown(jv["key"]);
1442 assert("key" in jv);
1443 assert("notAnElement" !in jv);
1444 assertThrown!JSONException(jv["notAnElement"]);
1445 const cjv = jv;
1446 assert("key" in cjv);
1447 assertThrown!JSONException(cjv["notAnElement"]);
1448
1449 foreach (string key, value; jv)
1450 {
1451 static assert(is(typeof(value) == JSONValue));
1452 assert(key == "key");
1453 assert(value.type == JSON_TYPE.STRING);
1454 assertNotThrown(value.str);
1455 assert(value.str == "value");
1456 }
1457
1458 jv = [3, 4, 5];
1459 assert(jv.type == JSON_TYPE.ARRAY);
1460 assertNotThrown(jv.array);
1461 assertNotThrown(jv[2]);
1462 foreach (size_t index, value; jv)
1463 {
1464 static assert(is(typeof(value) == JSONValue));
1465 assert(value.type == JSON_TYPE.INTEGER);
1466 assertNotThrown(value.integer);
1467 assert(index == (value.integer-3));
1468 }
1469
1470 jv = null;
1471 assert(jv.type == JSON_TYPE.NULL);
1472 assert(jv.isNull);
1473 jv = "foo";
1474 assert(!jv.isNull);
1475
1476 jv = JSONValue("value");
1477 assert(jv.type == JSON_TYPE.STRING);
1478 assert(jv.str == "value");
1479
1480 JSONValue jv2 = JSONValue("value");
1481 assert(jv2.type == JSON_TYPE.STRING);
1482 assert(jv2.str == "value");
1483
1484 JSONValue jv3 = JSONValue("\u001c");
1485 assert(jv3.type == JSON_TYPE.STRING);
1486 assert(jv3.str == "\u001C");
1487 }
1488
1489 @system unittest
1490 {
1491 // Bugzilla 11504
1492
1493 JSONValue jv = 1;
1494 assert(jv.type == JSON_TYPE.INTEGER);
1495
1496 jv.str = "123";
1497 assert(jv.type == JSON_TYPE.STRING);
1498 assert(jv.str == "123");
1499
1500 jv.integer = 1;
1501 assert(jv.type == JSON_TYPE.INTEGER);
1502 assert(jv.integer == 1);
1503
1504 jv.uinteger = 2u;
1505 assert(jv.type == JSON_TYPE.UINTEGER);
1506 assert(jv.uinteger == 2u);
1507
1508 jv.floating = 1.5;
1509 assert(jv.type == JSON_TYPE.FLOAT);
1510 assert(jv.floating == 1.5);
1511
1512 jv.object = ["key" : JSONValue("value")];
1513 assert(jv.type == JSON_TYPE.OBJECT);
1514 assert(jv.object == ["key" : JSONValue("value")]);
1515
1516 jv.array = [JSONValue(1), JSONValue(2), JSONValue(3)];
1517 assert(jv.type == JSON_TYPE.ARRAY);
1518 assert(jv.array == [JSONValue(1), JSONValue(2), JSONValue(3)]);
1519
1520 jv = true;
1521 assert(jv.type == JSON_TYPE.TRUE);
1522
1523 jv = false;
1524 assert(jv.type == JSON_TYPE.FALSE);
1525
1526 enum E{True = true}
1527 jv = E.True;
1528 assert(jv.type == JSON_TYPE.TRUE);
1529 }
1530
1531 @system pure unittest
1532 {
1533 // Adding new json element via array() / object() directly
1534
1535 JSONValue jarr = JSONValue([10]);
1536 foreach (i; 0 .. 9)
1537 jarr.array ~= JSONValue(i);
1538 assert(jarr.array.length == 10);
1539
1540 JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
1541 foreach (i; 0 .. 9)
1542 jobj.object[text("key", i)] = JSONValue(text("value", i));
1543 assert(jobj.object.length == 10);
1544 }
1545
1546 @system pure unittest
1547 {
1548 // Adding new json element without array() / object() access
1549
1550 JSONValue jarr = JSONValue([10]);
1551 foreach (i; 0 .. 9)
1552 jarr ~= [JSONValue(i)];
1553 assert(jarr.array.length == 10);
1554
1555 JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
1556 foreach (i; 0 .. 9)
1557 jobj[text("key", i)] = JSONValue(text("value", i));
1558 assert(jobj.object.length == 10);
1559
1560 // No array alias
1561 auto jarr2 = jarr ~ [1,2,3];
1562 jarr2[0] = 999;
1563 assert(jarr[0] == JSONValue(10));
1564 }
1565
1566 @system unittest
1567 {
1568 // @system because JSONValue.array is @system
1569 import std.exception;
1570
1571 // An overly simple test suite, if it can parse a serializated string and
1572 // then use the resulting values tree to generate an identical
1573 // serialization, both the decoder and encoder works.
1574
1575 auto jsons = [
1576 `null`,
1577 `true`,
1578 `false`,
1579 `0`,
1580 `123`,
1581 `-4321`,
1582 `0.25`,
1583 `-0.25`,
1584 `""`,
1585 `"hello\nworld"`,
1586 `"\"\\\/\b\f\n\r\t"`,
1587 `[]`,
1588 `[12,"foo",true,false]`,
1589 `{}`,
1590 `{"a":1,"b":null}`,
1591 `{"goodbye":[true,"or",false,["test",42,{"nested":{"a":23.5,"b":0.140625}}]],`
1592 ~`"hello":{"array":[12,null,{}],"json":"is great"}}`,
1593 ];
1594
1595 enum dbl1_844 = `1.8446744073709568`;
1596 version (MinGW)
1597 jsons ~= dbl1_844 ~ `e+019`;
1598 else
1599 jsons ~= dbl1_844 ~ `e+19`;
1600
1601 JSONValue val;
1602 string result;
1603 foreach (json; jsons)
1604 {
1605 try
1606 {
1607 val = parseJSON(json);
1608 enum pretty = false;
1609 result = toJSON(val, pretty);
1610 assert(result == json, text(result, " should be ", json));
1611 }
1612 catch (JSONException e)
1613 {
1614 import std.stdio : writefln;
1615 writefln(text(json, "\n", e.toString()));
1616 }
1617 }
1618
1619 // Should be able to correctly interpret unicode entities
1620 val = parseJSON(`"\u003C\u003E"`);
1621 assert(toJSON(val) == "\"\&lt;\&gt;\"");
1622 assert(val.to!string() == "\"\&lt;\&gt;\"");
1623 val = parseJSON(`"\u0391\u0392\u0393"`);
1624 assert(toJSON(val) == "\"\&Alpha;\&Beta;\&Gamma;\"");
1625 assert(val.to!string() == "\"\&Alpha;\&Beta;\&Gamma;\"");
1626 val = parseJSON(`"\u2660\u2666"`);
1627 assert(toJSON(val) == "\"\&spades;\&diams;\"");
1628 assert(val.to!string() == "\"\&spades;\&diams;\"");
1629
1630 //0x7F is a control character (see Unicode spec)
1631 val = parseJSON(`"\u007F"`);
1632 assert(toJSON(val) == "\"\\u007F\"");
1633 assert(val.to!string() == "\"\\u007F\"");
1634
1635 with(parseJSON(`""`))
1636 assert(str == "" && str !is null);
1637 with(parseJSON(`[]`))
1638 assert(!array.length);
1639
1640 // Formatting
1641 val = parseJSON(`{"a":[null,{"x":1},{},[]]}`);
1642 assert(toJSON(val, true) == `{
1643 "a": [
1644 null,
1645 {
1646 "x": 1
1647 },
1648 {},
1649 []
1650 ]
1651 }`);
1652 }
1653
1654 @safe unittest
1655 {
1656 auto json = `"hello\nworld"`;
1657 const jv = parseJSON(json);
1658 assert(jv.toString == json);
1659 assert(jv.toPrettyString == json);
1660 }
1661
1662 @system pure unittest
1663 {
1664 // Bugzilla 12969
1665
1666 JSONValue jv;
1667 jv["int"] = 123;
1668
1669 assert(jv.type == JSON_TYPE.OBJECT);
1670 assert("int" in jv);
1671 assert(jv["int"].integer == 123);
1672
1673 jv["array"] = [1, 2, 3, 4, 5];
1674
1675 assert(jv["array"].type == JSON_TYPE.ARRAY);
1676 assert(jv["array"][2].integer == 3);
1677
1678 jv["str"] = "D language";
1679 assert(jv["str"].type == JSON_TYPE.STRING);
1680 assert(jv["str"].str == "D language");
1681
1682 jv["bool"] = false;
1683 assert(jv["bool"].type == JSON_TYPE.FALSE);
1684
1685 assert(jv.object.length == 4);
1686
1687 jv = [5, 4, 3, 2, 1];
1688 assert( jv.type == JSON_TYPE.ARRAY );
1689 assert( jv[3].integer == 2 );
1690 }
1691
1692 @safe unittest
1693 {
1694 auto s = q"EOF
1695 [
1696 1,
1697 2,
1698 3,
1699 potato
1700 ]
1701 EOF";
1702
1703 import std.exception;
1704
1705 auto e = collectException!JSONException(parseJSON(s));
1706 assert(e.msg == "Unexpected character 'p'. (Line 5:3)", e.msg);
1707 }
1708
1709 // handling of special float values (NaN, Inf, -Inf)
1710 @safe unittest
1711 {
1712 import std.exception : assertThrown;
1713 import std.math : isNaN, isInfinity;
1714
1715 // expected representations of NaN and Inf
1716 enum {
1717 nanString = '"' ~ JSONFloatLiteral.nan ~ '"',
1718 infString = '"' ~ JSONFloatLiteral.inf ~ '"',
1719 negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"',
1720 }
1721
1722 // with the specialFloatLiterals option, encode NaN/Inf as strings
1723 assert(JSONValue(float.nan).toString(JSONOptions.specialFloatLiterals) == nanString);
1724 assert(JSONValue(double.infinity).toString(JSONOptions.specialFloatLiterals) == infString);
1725 assert(JSONValue(-real.infinity).toString(JSONOptions.specialFloatLiterals) == negativeInfString);
1726
1727 // without the specialFloatLiterals option, throw on encoding NaN/Inf
1728 assertThrown!JSONException(JSONValue(float.nan).toString);
1729 assertThrown!JSONException(JSONValue(double.infinity).toString);
1730 assertThrown!JSONException(JSONValue(-real.infinity).toString);
1731
1732 // when parsing json with specialFloatLiterals option, decode special strings as floats
1733 JSONValue jvNan = parseJSON(nanString, JSONOptions.specialFloatLiterals);
1734 JSONValue jvInf = parseJSON(infString, JSONOptions.specialFloatLiterals);
1735 JSONValue jvNegInf = parseJSON(negativeInfString, JSONOptions.specialFloatLiterals);
1736
1737 assert(jvNan.floating.isNaN);
1738 assert(jvInf.floating.isInfinity && jvInf.floating > 0);
1739 assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0);
1740
1741 // when parsing json without the specialFloatLiterals option, decode special strings as strings
1742 jvNan = parseJSON(nanString);
1743 jvInf = parseJSON(infString);
1744 jvNegInf = parseJSON(negativeInfString);
1745
1746 assert(jvNan.str == JSONFloatLiteral.nan);
1747 assert(jvInf.str == JSONFloatLiteral.inf);
1748 assert(jvNegInf.str == JSONFloatLiteral.negativeInf);
1749 }
1750
1751 pure nothrow @safe @nogc unittest
1752 {
1753 JSONValue testVal;
1754 testVal = "test";
1755 testVal = 10;
1756 testVal = 10u;
1757 testVal = 1.0;
1758 testVal = (JSONValue[string]).init;
1759 testVal = JSONValue[].init;
1760 testVal = null;
1761 assert(testVal.isNull);
1762 }
1763
1764 pure nothrow @safe unittest // issue 15884
1765 {
1766 import std.typecons;
1767 void Test(C)() {
1768 C[] a = ['x'];
1769 JSONValue testVal = a;
1770 assert(testVal.type == JSON_TYPE.STRING);
1771 testVal = a.idup;
1772 assert(testVal.type == JSON_TYPE.STRING);
1773 }
1774 Test!char();
1775 Test!wchar();
1776 Test!dchar();
1777 }
1778
1779 @safe unittest // issue 15885
1780 {
1781 enum bool realInDoublePrecision = real.mant_dig == double.mant_dig;
1782
1783 static bool test(const double num0)
1784 {
1785 import std.math : feqrel;
1786 const json0 = JSONValue(num0);
1787 const num1 = to!double(toJSON(json0));
1788 static if (realInDoublePrecision)
1789 return feqrel(num1, num0) >= (double.mant_dig - 1);
1790 else
1791 return num1 == num0;
1792 }
1793
1794 assert(test( 0.23));
1795 assert(test(-0.23));
1796 assert(test(1.223e+24));
1797 assert(test(23.4));
1798 assert(test(0.0012));
1799 assert(test(30738.22));
1800
1801 assert(test(1 + double.epsilon));
1802 assert(test(double.min_normal));
1803 static if (realInDoublePrecision)
1804 assert(test(-double.max / 2));
1805 else
1806 assert(test(-double.max));
1807
1808 const minSub = double.min_normal * double.epsilon;
1809 assert(test(minSub));
1810 assert(test(3*minSub));
1811 }
1812
1813 @safe unittest // issue 17555
1814 {
1815 import std.exception : assertThrown;
1816
1817 assertThrown!JSONException(parseJSON("\"a\nb\""));
1818 }
1819
1820 @safe unittest // issue 17556
1821 {
1822 auto v = JSONValue("\U0001D11E");
1823 auto j = toJSON(v, false, JSONOptions.escapeNonAsciiChars);
1824 assert(j == `"\uD834\uDD1E"`);
1825 }
1826
1827 @safe unittest // issue 5904
1828 {
1829 string s = `"\uD834\uDD1E"`;
1830 auto j = parseJSON(s);
1831 assert(j.str == "\U0001D11E");
1832 }
1833
1834 @safe unittest // issue 17557
1835 {
1836 assert(parseJSON("\"\xFF\"").str == "\xFF");
1837 assert(parseJSON("\"\U0001D11E\"").str == "\U0001D11E");
1838 }
1839
1840 @safe unittest // issue 17553
1841 {
1842 auto v = JSONValue("\xFF");
1843 assert(toJSON(v) == "\"\xFF\"");
1844 }
1845
1846 @safe unittest
1847 {
1848 import std.utf;
1849 assert(parseJSON("\"\xFF\"".byChar).str == "\xFF");
1850 assert(parseJSON("\"\U0001D11E\"".byChar).str == "\U0001D11E");
1851 }
1852
1853 @safe unittest // JSONOptions.doNotEscapeSlashes (issue 17587)
1854 {
1855 assert(parseJSON(`"/"`).toString == `"\/"`);
1856 assert(parseJSON(`"\/"`).toString == `"\/"`);
1857 assert(parseJSON(`"/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
1858 assert(parseJSON(`"\/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
1859 }