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 &#95;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 &#95;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 &#95;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 }