1 module painlessjson.painlessjson;
2
3 import std.algorithm : map;
4 import std..string;
5 import std.conv;
6 import std.json;
7 import std.range;
8 import std.traits;
9 import std.typecons : Tuple;
10 import std.typetuple : TypeTuple;
11 import painlessjson.annotations;
12 import painlessjson..string;
13 import painlesstraits;
14
15 version (unittest)
16 {
17 import std.stdio : writeln;
18 import painlessjson.unittesttypes;
19 import dunit.toolkit;
20 }
21
22 struct SerializationOptions
23 {
24 bool alsoAcceptUnderscore;
25 bool convertToUnderscore;
26 }
27
28 enum defaultSerializatonOptions = SerializationOptions(true, false);
29
30 static if (is(typeof(JSONType.integer)))
31 {
32 enum JSONType_int = JSONType.integer;
33 enum JSONType_true = JSONType.true_;
34 enum JSONType_null = JSONType.null_;
35 }
36 else
37 {
38 enum JSONType_int = JSON_TYPE.INTEGER;
39 enum JSONType_true = JSON_TYPE.TRUE;
40 enum JSONType_null = JSON_TYPE.NULL;
41 }
42
43
44
45 //See if we can use something else than __traits(compiles, (T t){JSONValue(t);})
46 private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (__traits(compiles, (in T t) {
47 JSONValue(t);
48 }))
49 {
50 return JSONValue(object);
51 }
52
53 //See if we can use something else than !__traits(compiles, (T t){JSONValue(t);})
54 private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (isArray!T && !__traits(compiles, (in T t) {
55 JSONValue(t);
56 }))
57 {
58 JSONValue[] jsonRange;
59 jsonRange = map!((el) => el.toJSON)(object).array;
60 return JSONValue(jsonRange);
61 }
62
63 // AA for simple types, excluding those handled by the JSONValue constructor
64 private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object)
65 if (isAssociativeArray!T && isBuiltinType!(KeyType!T) && !isAssociativeArray!(KeyType!T) &&
66 !__traits(compiles, (in T t) { JSONValue(t);}))
67 {
68 JSONValue[string] jsonAA;
69 foreach (key, value; object)
70 {
71
72 jsonAA[to!string(key)] = value.toJSON;
73 }
74 return JSONValue(jsonAA);
75 }
76
77 // AA for compound types
78 private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (isAssociativeArray!T && (!isBuiltinType!(KeyType!T) || isAssociativeArray!(KeyType!T)))
79 {
80 JSONValue[string] jsonAA;
81 foreach (key, value; object)
82 {
83
84 jsonAA[key.toJSON.toString] = value.toJSON;
85 }
86 return JSONValue(jsonAA);
87 }
88
89 // platform-independent tuple serializer using _0, _1, ... keys for unnamed keys (because key names could either be _0, _0UL or something else which is implmenetation defined)
90 private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (is(T : Tuple!U, U...))
91 {
92 JSONValue[string] jsonAA;
93 static foreach (i, name; T.fieldNames)
94 {
95 static if (name.length != 0)
96 jsonAA[name] = object[i].toJSON;
97 else
98 jsonAA["_" ~ (cast(int)i).to!string] = object[i].toJSON;
99 }
100 return JSONValue(jsonAA);
101 }
102
103 private JSONValue defaultToJSONImpl(T, SerializationOptions options )(in T object) if (!isBuiltinType!T
104 && !is(T : Tuple!U, U...) && !__traits(compiles, (in T t) { JSONValue(t); }))
105 {
106 JSONValue[string] json;
107 // Getting all member variables (there is probably an easier way)
108 foreach (name; __traits(allMembers, T))
109 {
110 static if (__traits(compiles,
111 {
112 json[serializationToName!(__traits(getMember, object, name), name, false)] = __traits(getMember,
113 object, name).toJSON;
114 }) && !hasAnyOfTheseAnnotations!(__traits(getMember, object,
115 name), SerializeIgnore, SerializeToIgnore)
116 && isFieldOrProperty!(__traits(getMember, object, name))
117 && __traits(getProtection, __traits(getMember, object, name)) != "private")
118 {
119 json[serializationToName!(__traits(getMember, object, name), name,(options.convertToUnderscore))] = __traits(getMember,
120 object, name).toJSON;
121 }
122 }
123 return JSONValue(json);
124 }
125
126 /++
127 Convert any type to JSON<br />
128 Can be overridden by _toJSON
129 +/
130 JSONValue defaultToJSON(T, SerializationOptions options = defaultSerializatonOptions)(in T t){
131 return defaultToJSONImpl!(T, options)(t);
132 }
133
134 /// Template function that converts any object to JSON
135 JSONValue toJSON(T, SerializationOptions options = defaultSerializatonOptions)(in T t)
136 {
137 static if (__traits(compiles, (in T t) { return t._toJSON(); }))
138 {
139 return t._toJSON();
140 }
141 else
142 return defaultToJSON!(T, options)(t);
143 }
144
145 /// Converting common types
146 unittest
147 {
148 assertEqual(5.toJSON!int, JSONValue(5));
149 assert(4.toJSON != JSONValue(5)); //TODO: Wait for DUnit to implement assertNotEqual
150 assertEqual(5.4.toJSON, JSONValue(5.4));
151 assertEqual(toJSON("test"), JSONValue("test"));
152 assertEqual(toJSON(JSONValue("test")), JSONValue("test"));
153 }
154
155 /// Converting InputRanges
156 unittest
157 {
158 assertEqual([1, 2].toJSON.toString, "[1,2]");
159 }
160
161 /// User structs
162 unittest
163 {
164 Point p;
165 assertEqual(toJSON(p).toString, q{{"x":0,"y":1}});
166 }
167
168 /// Array of structs
169 unittest
170 {
171 Point[] ps = [Point(-1, 1), Point(2, 3)];
172 assertEqual(toJSON(ps).toString, q{[{"x":-1,"y":1},{"x":2,"y":3}]});
173 }
174
175 /// User class
176 unittest
177 {
178 PointC p = new PointC(1, -2);
179 assertEqual(toJSON(p).toString, q{{"x":1,"y":-2}});
180 }
181
182 /// User class with private fields
183 unittest
184 {
185 PointPrivate p = new PointPrivate(-1, 2);
186 assertEqual(toJSON(p).toString, q{{"x":-1,"y":2}});
187 auto pnt = p.toJSON.fromJSON!PointPrivate;
188 assertEqual(p.x, -1);
189 assertEqual(p.y, 2);
190 }
191
192 /// User class with defaultToJSON
193 unittest
194 {
195 PointDefaultFromJSON p = new PointDefaultFromJSON(-1, 2);
196 assertEqual(toJSON(p).toString, q{{"_x":-1,"y":2}});
197 auto pnt = p.toJSON.fromJSON!PointDefaultFromJSON;
198 assertEqual(p.x, -1);
199 assertEqual(p.y, 2);
200 }
201
202 /// User class with private fields and @property
203 unittest
204 {
205 auto p = PointPrivateProperty(-1, 2);
206 assertEqual(toJSON(p).toString, q{{"x":-1,"y":2,"z":1}});
207 }
208
209 /// User class with SerializedName annotation
210 unittest
211 {
212 auto p = PointSerializationName(-1, 2);
213 assertEqual(toJSON(p)["xOut"].floating, -1);
214 assertEqual(toJSON(p)["yOut"].floating, 2);
215 }
216
217 /// User class with SerializeIgnore annotations
218 unittest
219 {
220 auto p = PointSerializationIgnore(-1, 5, 4);
221 assertEqual(toJSON(p).toString, q{{"z":5}});
222 }
223
224 /// Array of classes
225 unittest
226 {
227 PointC[] ps = [new PointC(-1, 1), new PointC(2, 3)];
228 assertEqual(toJSON(ps).toString, q{[{"x":-1,"y":1},{"x":2,"y":3}]});
229 }
230
231 /// Associative array
232 unittest
233 {
234 string[int] aa = [0 : "a", 1 : "b"];
235 assert(aa.toJSON.toString == q{{"0":"a","1":"b"}});
236 Point[int] aaStruct = [0 : Point(-1, 1), 1 : Point(2, 0)];
237 assertEqual(aaStruct.toJSON.toString, q{{"0":{"x":-1,"y":1},"1":{"x":2,"y":0}}});
238 assertEqual(["key": "value"].toJSON.toString, q{{"key":"value"}});
239 }
240
241 /// Associative array containing struct
242 unittest
243 {
244 assertEqual(["test": SimpleStruct("test2")].toJSON().toString, q{{"test":{"str":"test2"}}});
245 }
246
247 /// Associative array with struct key
248 unittest
249 {
250 assertEqual([SimpleStruct("key"): "value", SimpleStruct("key2"): "value2"].toJSON().toString, q{{"{\"str\":\"key\"}":"value","{\"str\":\"key2\"}":"value2"}});
251 }
252
253 /// struct with inner struct and AA
254 unittest
255 {
256 auto testStruct = StructWithStructAndAA(["key1": "value1"], ["key2": StructWithStructAndAA.Inner("value2")]);
257 auto converted = testStruct.toJSON();
258 assertEqual(converted["stringToInner"].toString, q{{"key2":{"str":"value2"}}});
259 assertEqual(converted["stringToString"].toString, q{{"key1":"value1"}});
260 }
261
262 /// Unnamed tuples
263 unittest
264 {
265 Tuple!(int, int) point;
266 point[0] = 5;
267 point[1] = 6;
268 assertEqual(toJSON(point).toString, q{{"_0":5,"_1":6}});
269 }
270
271 /// Named tuples
272 unittest
273 {
274 Tuple!(int, "x", int, "y") point;
275 point.x = 5;
276 point.y = 6;
277 assertEqual(point, fromJSON!(Tuple!(int, "x", int, "y"))(parseJSON(q{{"x":5,"y":6}})));
278 }
279
280 /// Convert camel case to underscore automatically
281 unittest
282 {
283 CamelCaseConversion value;
284 value.wasCamelCase = 5;
285 value.was_underscore = 7;
286
287 auto valueAsJSON = value.toJSON!(CamelCaseConversion,SerializationOptions(false, true));
288
289 assertEqual(valueAsJSON["was_camel_case"].integer, 5);
290 assertEqual(valueAsJSON["was_underscore"].integer, 7);
291 }
292
293 /// Overloaded toJSON
294 unittest
295 {
296 class A
297 {
298 double x = 0;
299 double y = 1;
300 JSONValue toJSON()
301 {
302 JSONValue[string] json;
303 json["x"] = x;
304 return JSONValue(json);
305 }
306
307 }
308
309 auto a = new A;
310 assertEqual(a.toJSON.toString, q{{"x":0}});
311
312 class B
313 {
314 double x = 0;
315 double y = 1;
316 }
317
318 // Both templates will now work for B, so this is ambiguous in D.
319 // Under dmd it looks like the toJSON!T that is loaded first is the one used
320 JSONValue toJSON(T : B)(T b)
321 {
322 JSONValue[string] json;
323 json["x"] = b.x;
324 return JSONValue(json);
325 }
326
327 auto b = new B;
328 assertEqual(b.toJSON.toString, q{{"x":0,"y":1}});
329
330 class Z
331 {
332 double x = 0;
333 double y = 1;
334 // Adding an extra value
335 JSONValue toJSON()
336 {
337 JSONValue[string] json = painlessjson.toJSON!Z(this).object;
338 json["add"] = "bla".toJSON;
339 return JSONValue(json);
340 }
341
342 }
343
344 auto z = new Z;
345 assertEqual(z.toJSON["x"].floating, 0);
346 assertEqual(z.toJSON["y"].floating, 1);
347 assertEqual(z.toJSON["add"].str, "bla");
348 }
349
350 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (is(T == JSONValue))
351 {
352 return json;
353 }
354
355 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isIntegral!T)
356 {
357 return to!T(json.integer);
358 }
359
360 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isFloatingPoint!T)
361 {
362 if (json.type == JSONType_int)
363 return to!T(json.integer);
364 else
365 return to!T(json.floating);
366 }
367
368 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (is(T == string))
369 {
370 return to!T(json.str);
371 }
372
373 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isBoolean!T)
374 {
375 if (json.type == JSONType_true)
376 return true;
377 else
378 return false;
379 }
380
381 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isArray!T && !is(T == string))
382 {
383 T t; //Se is we can find another way of finding t.front
384 return map!((js) => fromJSON!(typeof(t.front))(js))(json.array).array;
385 }
386
387 // AA for simple keys
388 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isAssociativeArray!T && isBuiltinType!(KeyType!T) && !isAssociativeArray!(KeyType!T))
389 {
390 T t;
391 const JSONValue[string] jsonAA = json.object;
392 foreach (k, v; jsonAA)
393 {
394 t[to!(KeyType!T)(k)] = fromJSON!(ValueType!T)(v);
395 }
396 return t;
397 }
398
399 // AA for compound keys
400 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isAssociativeArray!T && (!isBuiltinType!(KeyType!T) || isAssociativeArray!(KeyType!T)))
401 {
402 KeyType!T keyConverter(string stringKey) {
403 try{
404 return fromJSON!(KeyType!T)(parseJSON(stringKey));
405 }
406 catch(JSONException) {
407 throw new Exception("Couldn't convert JSON key \"" ~ stringKey ~ "\" to " ~ KeyType!T.stringof ~ " which is the key type of " ~ T.stringof);
408 }
409 }
410
411 T t;
412 const JSONValue[string] jsonAA = json.object;
413 foreach (k, v; jsonAA)
414 {
415 t[keyConverter(k)] = fromJSON!(ValueType!T)(v);
416
417 }
418 return t;
419 }
420
421 // Default tuple implementation
422 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (is(T : Tuple!U, U...))
423 {
424 T t;
425 const JSONValue[string] jsonAA = json.object;
426 static foreach (i, name; T.fieldNames)
427 {{
428 static if (name.length != 0)
429 enum effectiveName = name;
430 else
431 enum effectiveName = "_" ~ (cast(int)i).to!string;
432
433 if (auto v = effectiveName in jsonAA)
434 t[i] = fromJSON!(typeof(t[i]))(*v);
435 }}
436 return t;
437 }
438
439 /++
440 Convert to given type from JSON.<br />
441 Can be overridden by _fromJSON.
442 +/
443 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json)
444 if (!isBuiltinType!T && !is(T == JSONValue) && !is(T : Tuple!U, U...))
445 {
446 static if (hasAccessibleDefaultsConstructor!(T))
447 {
448 T t = getIntstanceFromDefaultConstructor!T;
449 mixin("const JSONValue[string] jsonAA = json.object;");
450 foreach (name; __traits(allMembers, T))
451 {
452
453 static if (__traits(compiles,{mixin("import " ~ moduleName!(__traits(getMember, t, name)) ~ ";");}))
454 {
455 mixin("import " ~ moduleName!(__traits(getMember, t, name)) ~ ";");
456 }
457
458 static if (__traits(compiles,
459 mixin(
460 "t." ~ name ~ "= fromJSON!(" ~ fullyQualifiedName!(typeof(__traits(getMember,
461 t, name))) ~ ")(jsonAA[\"aFieldName\"])"))
462 && !hasAnyOfTheseAnnotations!(__traits(getMember, t, name),
463 SerializeIgnore, SerializeFromIgnore)
464 && isFieldOrProperty!(__traits(getMember, t, name)))
465 {
466 enum string fromName = serializationFromName!(__traits(getMember, t,
467 name), name);
468 mixin(
469 "if ( \"" ~ fromName ~ "\" in jsonAA) t." ~ name ~ "= fromJSON!(" ~ fullyQualifiedName!(
470 typeof(__traits(getMember, t, name))) ~ ")(jsonAA[\"" ~ fromName ~ "\"]);");
471 static if(options.alsoAcceptUnderscore)
472 {
473 mixin(
474 "if ( \"" ~ camelCaseToUnderscore(fromName) ~ "\" in jsonAA) t." ~ name ~ "= fromJSON!(" ~ fullyQualifiedName!(
475 typeof(__traits(getMember, t, name))) ~ ")(jsonAA[\"" ~ camelCaseToUnderscore(fromName) ~ "\"]);");
476 }
477 }
478 }
479 return t;
480 }
481 else static if (hasAccessibleConstructor!T)
482 {
483 if (__traits(hasMember, T, "__ctor"))
484 {
485 alias Overloads = TypeTuple!(__traits(getOverloads, T, "__ctor"));
486 alias constructorFunctionType = T function(JSONValue value) @system;
487 ulong bestOverloadScore = ulong.max;
488 constructorFunctionType bestOverload;
489
490 // Find the constructor overloads that matches our json content the best
491 foreach (overload; Overloads)
492 {
493 static if (__traits(compiles,
494 {
495 return getInstanceFromCustomConstructor!(T, overload, false)(json);
496 }))
497 {
498 if (jsonValueHasAllFieldsNeeded!(overload, (options.alsoAcceptUnderscore))(json))
499 {
500 ulong overloadScore = constructorOverloadScore!(overload, (options.alsoAcceptUnderscore))(json);
501 if (overloadScore < bestOverloadScore)
502 {
503 bestOverload = function(JSONValue value) {
504 return getInstanceFromCustomConstructor!(T, overload, (options.alsoAcceptUnderscore))(value);
505 };
506 bestOverloadScore = overloadScore;
507 }
508 }
509 }
510 }
511 if (bestOverloadScore < ulong.max)
512 {
513 return bestOverload(json);
514 }
515 throw new JSONException(
516 "JSONValue can't satisfy any constructor: " ~ json.toPrettyString);
517 }
518 }
519 else static assert(false, "Cannot be automatically generate a defaultFromJSONImpl for type " ~ T.stringof
520 ~ ". (no accessible and suitable constructors) Create a _fromJSON member method for custom deserialization.");
521 }
522
523 /++
524 Convert to given type from JSON.<br />
525 Can be overridden by _fromJSON.
526 +/
527 T defaultFromJSON(T, SerializationOptions options = defaultSerializatonOptions)(in JSONValue json){
528 return defaultFromJSONImpl!(T, options)(json);
529
530 }
531
532 template hasAccessibleDefaultsConstructor(T)
533 {
534 static bool helper()
535 {
536 return (is(T == struct) && __traits(compiles, { T t; }))
537 || (is(T == class) && __traits(compiles, { T t = new T; }));
538 }
539
540 enum bool hasAccessibleDefaultsConstructor = helper();
541 }
542
543 T getIntstanceFromDefaultConstructor(T)()
544 {
545 static if (is(T == struct) && __traits(compiles, { T t; }))
546 {
547 return T();
548 }
549 else static if (is(T == class) && __traits(compiles, { T t = new T; }))
550 {
551 return new T();
552 }
553 }
554
555 T getInstanceFromCustomConstructor(T, alias Ctor, bool alsoAcceptUnderscore)(in JSONValue json)
556 {
557 enum params = ParameterIdentifierTuple!(Ctor);
558 alias defaults = ParameterDefaultValueTuple!(Ctor);
559 alias Types = ParameterTypeTuple!(Ctor);
560 Tuple!(Types) args;
561 static foreach (i; 0 .. params.length)
562 {{
563 enum paramName = params[i];
564 if (paramName in json.object)
565 {
566 args[i] = fromJSON!(Types[i])(json[paramName]);
567 } else if( alsoAcceptUnderscore && camelCaseToUnderscore(paramName)){
568 args[i] = fromJSON!(Types[i])(json[camelCaseToUnderscore(paramName)]);
569 }
570 else
571 {
572 // no value specified in json
573 static if (is(defaults[i] == void))
574 {
575 throw new JSONException(
576 "parameter " ~ paramName ~ " has no default value and was not specified");
577 }
578 else
579 {
580 args[i] = defaults[i];
581 }
582 }
583 }}
584 static if (is(T == class))
585 {
586 return new T(args.expand);
587 }
588 else
589 {
590 return T(args.expand);
591 }
592 }
593
594 bool jsonValueHasAllFieldsNeeded(alias Ctor, bool alsoAcceptUnderscore)(in JSONValue json)
595 {
596 enum params = ParameterIdentifierTuple!(Ctor);
597 alias defaults = ParameterDefaultValueTuple!(Ctor);
598 alias Types = ParameterTypeTuple!(Ctor);
599 Tuple!(Types) args;
600 static foreach (i; 0 .. params.length)
601 {{
602 enum paramName = params[i];
603 if (!((paramName in json.object) || ( alsoAcceptUnderscore && (camelCaseToUnderscore(paramName) in json.object))) && is(defaults[i] == void))
604 {
605 return false;
606 }
607 }}
608 return true;
609 }
610
611 ulong constructorOverloadScore(alias Ctor, bool alsoAcceptUnderscore)(in JSONValue json)
612 {
613 enum params = ParameterIdentifierTuple!(Ctor);
614 alias defaults = ParameterDefaultValueTuple!(Ctor);
615 alias Types = ParameterTypeTuple!(Ctor);
616 Tuple!(Types) args;
617 ulong overloadScore = json.object.length;
618 static foreach (i; 0 .. params.length)
619 {{
620 enum paramName = params[i];
621 if (paramName in json.object || ( alsoAcceptUnderscore && (camelCaseToUnderscore(paramName) in json.object)))
622 {
623 overloadScore--;
624 }
625 }}
626 return overloadScore;
627 }
628
629 template hasAccessibleConstructor(T)
630 {
631 static bool helper()
632 {
633 if (__traits(hasMember, T, "__ctor"))
634 {
635 alias Overloads = TypeTuple!(__traits(getOverloads, T, "__ctor"));
636 bool anyInstanciates = false;
637 foreach (overload; Overloads)
638 {
639 anyInstanciates |= __traits(compiles, getInstanceFromCustomConstructor!(T,
640 overload, false)(JSONValue()));
641 }
642 return anyInstanciates;
643 }
644 }
645
646 enum bool hasAccessibleConstructor = helper();
647 }
648
649 /// Convert from JSONValue to any other type
650 T fromJSON(T, SerializationOptions options = defaultSerializatonOptions)(in JSONValue json)
651 {
652 static if (__traits(compiles, { return T._fromJSON(json); }))
653 {
654 return T._fromJSON(json);
655 }
656 else
657 {
658 if (json.type == JSONType_null)
659 return T.init;
660 return defaultFromJSON!(T,options)(json);
661 }
662 }
663
664 unittest {
665 struct P
666 { string name; }
667
668 auto jv = parseJSON(`{"name": null}`);
669 auto j = fromJSON!P(jv);
670 }
671
672 /// Converting common types
673 unittest
674 {
675 assertEqual(fromJSON!int(JSONValue(1)), 1);
676 assertEqual(fromJSON!double(JSONValue(1.0)), 1);
677 assertEqual(fromJSON!double(JSONValue(1.3)), 1.3);
678 assertEqual(fromJSON!string(JSONValue("str")), "str");
679 assertEqual(fromJSON!bool(JSONValue(true)), true);
680 assertEqual(fromJSON!bool(JSONValue(false)), false);
681 assertEqual(fromJSON!JSONValue(JSONValue(true)), JSONValue(true));
682 }
683
684 /// Converting arrays
685 unittest
686 {
687 assertEqual(fromJSON!(int[])(toJSON([1, 2])), [1, 2]);
688 assertEqual(fromJSON!(Point[])(parseJSON(q{[{"x":-1,"y":2},{"x":3,"y":4}]})), [Point(-1,2),Point(3,4)]);
689 }
690
691 /// Array as member of other class
692 unittest
693 {
694 // Types need to be defined in different module, otherwise
695 // type is not known at compile time
696 import painlessjson.unittesttypes_local_import;
697
698 string jsonString = q{[ {"duration": "10"} ]};
699 Route[] routes = parseJSON(jsonString).fromJSON!(Route[]);
700 assertEqual(routes.length, 1);
701
702 jsonString = q{{"routes":[ {"duration": "10"} ] }};
703 JourneyPlan jp;
704 jp = parseJSON(jsonString).fromJSON!JourneyPlan;
705 assertEqual(jp.routes.length, 1);
706 }
707
708 /// Associative arrays
709 unittest
710 {
711 string[int] aaInt = [0 : "a", 1 : "b"];
712 assertEqual(aaInt, fromJSON!(string[int])(parseJSON(q{{"0" : "a", "1": "b"}})));
713
714 string[string] aaString = ["hello" : "world", "json" : "painless"];
715 assertEqual(aaString, fromJSON!(string[string])(parseJSON(q{{"hello" : "world", "json" : "painless"}})));
716 }
717
718 /// Associative array containing struct
719 unittest
720 {
721 auto parsed = fromJSON!(SimpleStruct[string])(parseJSON(q{{"key": {"str": "value"}}}));
722 assertEqual(parsed , ["key": SimpleStruct("value")]);
723 }
724
725 /// Associative array with struct key
726 unittest
727 {
728 JSONValue value = parseJSON(q{{"{\"str\":\"key\"}":"value", "{\"str\":\"key2\"}":"value2"}});
729 auto parsed = fromJSON!(string[SimpleStruct])(value);
730 assertEqual(
731 parsed
732 , [SimpleStruct("key"): "value", SimpleStruct("key2"): "value2"]);
733 }
734
735 /// struct with inner struct and AA
736 unittest
737 {
738 auto testStruct = StructWithStructAndAA(["key1": "value1"], ["key2": StructWithStructAndAA.Inner("value2")]);
739 auto testJSON = parseJSON(q{{"stringToInner":{"key2":{"str":"value2"}},"stringToString":{"key1":"value1"}}});
740 assertEqual(fromJSON!StructWithStructAndAA(testJSON), testStruct);
741 }
742
743 /// Error reporting from inner objects
744 unittest
745 {
746 import std.exception : collectExceptionMsg;
747 import std.algorithm : canFind;
748 void throwFunc() {
749 fromJSON!(string[SimpleStruct])(parseJSON(q{{"{\"str\": \"key1\"}": "value", "key2":"value2"}}));
750 }
751 auto errorMessage = collectExceptionMsg(throwFunc());
752 assert(errorMessage.canFind("key2"));
753 assert(errorMessage.canFind("string[SimpleStruct]"));
754 assert(!errorMessage.canFind("key1"));
755 }
756
757 /// Structs from JSON
758 unittest
759 {
760 auto p = fromJSON!Point(parseJSON(q{{"x":-1,"y":2}}));
761 assertEqual(p.x, -1);
762 assertEqual(p.y, 2);
763 p = fromJSON!Point(parseJSON(q{{"x":2}}));
764 assertEqual(p.x, 2);
765 assertEqual(p.y, 1);
766 p = fromJSON!Point(parseJSON(q{{"y":3}}));
767 assertEqual(p.x, 0);
768 assertEqual(p.y, 3);
769 p = fromJSON!Point(parseJSON(q{{"x":-1,"y":2,"z":3}}));
770 assertEqual(p.x, -1);
771 assertEqual(p.y, 2);
772 }
773
774 /// Class from JSON
775 unittest
776 {
777 auto p = fromJSON!PointC(parseJSON(q{{"x":-1,"y":2}}));
778 assertEqual(p.x, -1);
779 assertEqual(p.y, 2);
780 }
781
782 /**
783 Convert class from JSON using "_fromJSON"
784 */
785
786 unittest
787 {
788 auto p = fromJSON!PointPrivate(parseJSON(q{{"x":-1,"y":2}}));
789 assertEqual(p.x, -1);
790 assertEqual(p.y, 2);
791 }
792
793 /// Convert struct from JSON using properties
794
795 unittest
796 {
797 auto p = fromJSON!PointPrivateProperty(parseJSON(q{{"x":-1,"y":2,"z":3}}));
798 assertEqual(p.x, -1);
799 assertEqual(p.y, 2);
800 }
801
802 /// User class with SerializedName annotation
803 unittest
804 {
805 auto p = fromJSON!PointSerializationName(parseJSON(q{{"xOut":-1,"yOut":2}}));
806 assertEqual(p.x, 2);
807 assertEqual(p.y, -1);
808 }
809
810 /// User class with SerializeIgnore annotations
811 unittest
812 {
813 auto p = fromJSON!PointSerializationIgnore(parseJSON(q{{"z":15}}));
814 assertEqual(p.x, 0);
815 assertEqual(p.y, 1);
816 assertEqual(p.z, 15);
817 }
818
819 /// Unnamed tuples
820 unittest
821 {
822 Tuple!(int, int) point;
823 point[0] = 5;
824 point[1] = 6;
825 assertEqual(point, fromJSON!(Tuple!(int, int))(parseJSON(q{{"_0":5,"_1":6}})));
826 }
827
828 /// No default constructor
829 unittest
830 {
831 auto p = fromJSON!PointUseConstructor(parseJSON(q{{"x":2, "y":5}}));
832 assertEqual(p.x, 2);
833 assertEqual(p.y, 5);
834 }
835
836 /// Multiple constructors and all JSON-values are there
837 unittest
838 {
839 auto person = fromJSON!IdAndName(parseJSON(q{{"id":34, "name": "Jason Pain"}}));
840 assertEqual(person.id, 34);
841 assertEqual(person.name, "Jason Pain");
842 }
843
844 /// Multiple constructors and some JSON-values are missing
845 unittest
846 {
847 auto person = fromJSON!IdAndName(parseJSON(q{{"id":34}}));
848 assertEqual(person.id, 34);
849 assertEqual(person.name, "Undefined");
850 }
851
852 /// Accept underscore and convert it to camelCase automatically
853 unittest
854 {
855 auto value = fromJSON!CamelCaseConversion(parseJSON(q{{"was_camel_case":8,"was_underscore":9}}));
856 assertEqual(value.wasCamelCase, 8);
857 assertEqual(value.was_underscore, 9);
858 }