1 /**
2   * Contains functions for deserializing JSON data.
3   *
4   * Authors: <a href="https://github.com/rcorre">rcorre</a>
5   * License: <a href="http://opensource.org/licenses/MIT">MIT</a>
6   * Copyright: Copyright © 2015, rcorre
7   * Date: 3/23/15
8   */
9 module jsonizer.fromjson;
10 
11 import std.json;
12 import std.conv;
13 import std.file;
14 import std.meta;
15 import std.range;
16 import std.traits;
17 import std.string;
18 import std.algorithm;
19 import std.exception;
20 import std.typecons;
21 import jsonizer.exceptions;
22 import jsonizer.common;
23 
24 // HACK: this is a hack to allow referencing this particular overload using
25 // &fromJSON!T in the JsonizeMe mixin
26 T _fromJSON(T)(JSONValue json,
27                in ref JsonizeOptions options = JsonizeOptions.defaults)
28 {
29   return fromJSON!T(json, options);
30 }
31 
32 /**
33  * Deserialize json into a value of type `T`.
34  *
35  * Params:
36  *  T       = Target type. can be any primitive/builtin D type, or any
37  *            user-defined type using the `JsonizeMe` mixin.
38  *  json    = `JSONValue` to deserialize.
39  *  options = configures the deserialization behavior.
40  */
41 T fromJSON(T)(JSONValue json,
42               in ref JsonizeOptions options = JsonizeOptions.defaults)
43 {
44   // JSONValue -- identity
45   static if (is(T == JSONValue))
46       return json;
47 
48   // enumeration
49   else static if (is(T == enum)) {
50     enforceJsonType!T(json, JSONType..string);
51     return to!T(json.str);
52   }
53 
54   // boolean
55   else static if (is(T : bool)) {
56     if (json.type == JSONType.true_)
57       return true;
58     else if (json.type == JSONType.false_)
59       return false;
60 
61     // expected 'true' or 'false'
62     throw new JsonizeTypeException(typeid(bool), json, JSONType.true_, JSONType.false_);
63   }
64 
65   // string
66   else static if (is(T : string)) {
67     if (json.type == JSONType.null_) { return null; }
68     enforceJsonType!T(json, JSONType..string);
69     return cast(T) json.str;
70   }
71 
72   // numeric
73   else static if (is(T : real)) {
74     switch(json.type) {
75       case JSONType.float_:
76         return cast(T) json.floating;
77       case JSONType.integer:
78         return cast(T) json.integer;
79       case JSONType.uinteger:
80         return cast(T) json.uinteger;
81       case JSONType..string:
82         enforce(json.str.isNumeric, format("tried to extract %s from json string %s", T.stringof, json.str));
83         return to!T(json.str); // try to parse string as int
84       default:
85     }
86 
87     throw new JsonizeTypeException(typeid(bool), json,
88                                    JSONType.float_, JSONType.integer, JSONType.uinteger, JSONType..string);
89   }
90 
91   // array
92   else static if (isArray!T) {
93     if (json.type == JSONType.null_) { return T.init; }
94     enforceJsonType!T(json, JSONType.array);
95     alias ElementType = ForeachType!T;
96     T vals;
97     foreach(idx, val ; json.array) {
98       static if (isStaticArray!T) {
99         vals[idx] = val.fromJSON!ElementType(options);
100       }
101       else {
102         vals ~= val.fromJSON!ElementType(options);
103       }
104     }
105     return vals;
106   }
107 
108   // associative array
109   else static if (isAssociativeArray!T) {
110     static assert(is(KeyType!T : string), "toJSON requires string keys for associative array");
111     if (json.type == JSONType.null_) { return null; }
112     enforceJsonType!T(json, JSONType.object);
113     alias ValType = ValueType!T;
114     T map;
115     foreach(key, val ; json.object) {
116       map[key] = fromJSON!ValType(val, options);
117     }
118     return map;
119   }
120 
121   // user-defined class or struct
122   else static if (!isBuiltinType!T) {
123     return fromJSONImpl!T(json, null, options);
124   }
125 
126   // by the time we get here, we've tried pretty much everything
127   else {
128     static assert(0, "fromJSON can't handle a " ~ T.stringof);
129   }
130 }
131 
132 /// Extract booleans from json values.
133 unittest {
134   assert(JSONValue(false).fromJSON!bool == false);
135   assert(JSONValue(true).fromJSON!bool == true);
136 }
137 
138 /// Extract a string from a json string.
139 unittest {
140   assert(JSONValue("asdf").fromJSON!string == "asdf");
141 }
142 
143 /// Extract various numeric types.
144 unittest {
145   assert(JSONValue(1).fromJSON!int      == 1);
146   assert(JSONValue(2u).fromJSON!uint    == 2u);
147   assert(JSONValue(3.0).fromJSON!double == 3.0);
148 
149   // fromJSON accepts numeric strings when a numeric conversion is requested
150   assert(JSONValue("4").fromJSON!long   == 4L);
151 }
152 
153 /// Convert a json string into an enum value.
154 unittest {
155   enum Category { one, two }
156   assert(JSONValue("one").fromJSON!Category == Category.one);
157 }
158 
159 /// Convert a json array into an array.
160 unittest {
161   auto a = [ 1, 2, 3 ];
162   assert(JSONValue(a).fromJSON!(int[]) == a);
163 }
164 
165 /// Convert a json object to an associative array.
166 unittest {
167   auto aa = ["a": 1, "b": 2];
168   assert(JSONValue(aa).fromJSON!(int[string]) == aa);
169 }
170 
171 /// Convert a json object to a user-defined type.
172 /// See the docs for `JsonizeMe` for more detailed examples.
173 unittest {
174   import jsonizer.jsonize;
175   static struct MyStruct {
176     mixin JsonizeMe;
177 
178     @jsonize int i;
179     @jsonize string s;
180     float f;
181   }
182 
183   auto json = `{ "i": 5, "s": "tally-ho!" }`.parseJSON;
184   auto val = json.fromJSON!MyStruct;
185   assert(val.i == 5);
186   assert(val.s == "tally-ho!");
187 }
188 
189 /**
190  * Extract a value from a json object by its key.
191  *
192  * Throws if `json` is not of `JSONType.object` or the key does not exist.
193  *
194  * Params:
195  *  T       = Target type. can be any primitive/builtin D type, or any
196  *            user-defined type using the `JsonizeMe` mixin.
197  *  json    = `JSONValue` to deserialize.
198  *  key     = key of desired value within the object.
199  *  options = configures the deserialization behavior.
200  */
201 T fromJSON(T)(JSONValue json,
202               string key,
203               in ref JsonizeOptions options = JsonizeOptions.defaults)
204 {
205   enforceJsonType!T(json, JSONType.object);
206   enforce(key in json.object, "tried to extract non-existent key " ~ key ~ " from JSONValue");
207   return fromJSON!T(json.object[key], options);
208 }
209 
210 /// Directly extract values from an object by their keys.
211 unittest {
212   auto aa = ["a": 1, "b": 2];
213   auto json = JSONValue(aa);
214   assert(json.fromJSON!int("a") == 1);
215   assert(json.fromJSON!ulong("b") == 2L);
216 }
217 
218 /// Deserialize an instance of a user-defined type from a json object.
219 unittest {
220   import jsonizer.jsonize;
221 
222   static struct Foo {
223     mixin JsonizeMe;
224     @jsonize int i;
225     @jsonize string[] a;
226   }
227 
228   auto foo = `{"i": 12, "a": [ "a", "b" ]}`.parseJSON.fromJSON!Foo;
229   assert(foo == Foo(12, [ "a", "b" ]));
230 }
231 
232 /**
233  * Extract a value from a json object by its key.
234  *
235  * Throws if `json` is not of `JSONType.object`.
236  * Return `defaultVal` if the key does not exist.
237  *
238  * Params:
239  *  T          = Target type. can be any primitive/builtin D type, or any
240  *               user-defined type using the `JsonizeMe` mixin.
241  *  json       = `JSONValue` to deserialize.
242  *  key        = key of desired value within the object.
243  *  defaultVal = value to return if key is not found
244  *  options    = configures the deserialization behavior.
245  */
246 T fromJSON(T)(JSONValue json,
247               string key,
248               T defaultVal,
249               in ref JsonizeOptions options = JsonizeOptions.defaults)
250 {
251   enforceJsonType!T(json, JSONType.object);
252   return (key in json.object) ? fromJSON!T(json.object[key]) : defaultVal;
253 }
254 
255 /// Substitute default values when keys aren't present.
256 unittest {
257   auto aa = ["a": 1, "b": 2];
258   auto json = JSONValue(aa);
259   assert(json.fromJSON!int("a", 7) == 1);
260   assert(json.fromJSON!int("c", 7) == 7);
261 }
262 
263 /*
264  * Convert a json value in its string representation into a type `T`
265  * Params:
266  *  T       = Target type. can be any primitive/builtin D type, or any
267  *            user-defined type using the `JsonizeMe` mixin.
268  *  json    = JSON-formatted string to deserialize.
269  *  options = configures the deserialization behavior.
270  */
271 T fromJSONString(T)(string json,
272                     in ref JsonizeOptions options = JsonizeOptions.defaults)
273 {
274   return fromJSON!T(json.parseJSON, options);
275 }
276 
277 /// Use `fromJSONString` to parse from a json `string` rather than a `JSONValue`
278 unittest {
279   assert(fromJSONString!(int[])("[1, 2, 3]") == [1, 2, 3]);
280 }
281 
282 /**
283  * Read a json-constructable object from a file.
284  * Params:
285  *  T       = Target type. can be any primitive/builtin D type, or any
286  *            user-defined type using the `JsonizeMe` mixin.
287  *  path    = filesystem path to json file
288  *  options = configures the deserialization behavior.
289  */
290 T readJSON(T)(string path,
291               in ref JsonizeOptions options = JsonizeOptions.defaults)
292 {
293   auto json = parseJSON(readText(path));
294   return fromJSON!T(json, options);
295 }
296 
297 /// Read a json file directly into a specified D type.
298 unittest {
299   import std.path : buildPath;
300   import std.uuid : randomUUID;
301   import std.file : tempDir, write, mkdirRecurse;
302 
303   auto dir = buildPath(tempDir(), "jsonizer_readjson_test");
304   mkdirRecurse(dir);
305   auto file = buildPath(dir, randomUUID().toString);
306 
307   file.write("[1, 2, 3]");
308 
309   assert(file.readJSON!(int[]) == [ 1, 2, 3 ]);
310 }
311 
312 /**
313  * Read the contents of a json file directly into a `JSONValue`.
314  * Params:
315  *   path = filesystem path to json file
316  */
317 auto readJSON(string path) {
318   return parseJSON(readText(path));
319 }
320 
321 /// Read a json file into a JSONValue.
322 unittest {
323   import std.path : buildPath;
324   import std.uuid : randomUUID;
325   import std.file : tempDir, write, mkdirRecurse;
326 
327   auto dir = buildPath(tempDir(), "jsonizer_readjson_test");
328   mkdirRecurse(dir);
329   auto file = buildPath(dir, randomUUID().toString);
330 
331   file.write("[1, 2, 3]");
332 
333   auto json = file.readJSON();
334 
335   assert(json.array[0].integer == 1);
336   assert(json.array[1].integer == 2);
337   assert(json.array[2].integer == 3);
338 }
339 
340 deprecated("use fromJSON instead") {
341   /// Deprecated: use `fromJSON` instead.
342   T extract(T)(JSONValue json) {
343     return json.fromJSON!T;
344   }
345 }
346 
347 private:
348 void enforceJsonType(T)(JSONValue json, JSONType[] expected ...) {
349   if (!expected.canFind(json.type)) {
350     throw new JsonizeTypeException(typeid(T), json, expected);
351   }
352 }
353 
354 unittest {
355   import std.exception : assertThrown, assertNotThrown;
356   with (JSONType) {
357     assertThrown!JsonizeTypeException(enforceJsonType!int(JSONValue("hi"), integer, uinteger));
358     assertThrown!JsonizeTypeException(enforceJsonType!(bool[string])(JSONValue([ 5 ]), object));
359     assertNotThrown(enforceJsonType!int(JSONValue(5), integer, uinteger));
360     assertNotThrown(enforceJsonType!(bool[string])(JSONValue(["key": true]), object));
361   }
362 }
363 
364 // Internal implementation of fromJSON for user-defined types
365 // If T is a nested class, pass the parent of type P
366 // otherwise pass null for the parent
367 T fromJSONImpl(T, P)(JSONValue json, P parent, in ref JsonizeOptions options) {
368   static if (is(typeof(null) : T)) {
369     if (json.type == JSONType.null_) { return null; }
370   }
371 
372   // try constructing from a primitive type using a single-param constructor
373   if (json.type != JSONType.object) {
374     return invokePrimitiveCtor!T(json, parent);
375   }
376 
377   static if (!isNested!T && is(T == class))
378   {
379     // if the class is identified in the json, construct an instance of the
380     // specified type
381     auto className = json.fromJSON!string(options.classKey, null);
382     if (options.classMap) {
383         if(auto tmp = options.classMap(className))
384           className = tmp;
385     }
386     if (className && className != fullyQualifiedName!T) {
387       auto handler = className in T._jsonizeCtors;
388       assert(handler, className ~ " not registered in " ~ T.stringof);
389       JsonizeOptions newopts = options;
390       newopts.classKey = null; // don't recursively loop looking up class name
391       return (*handler)(json, newopts);
392     }
393   }
394 
395   // next, try to find a contructor marked with @jsonize and call that
396   static if (__traits(hasMember, T, "__ctor")) {
397     alias Overloads = AliasSeq!(__traits(getOverloads, T, "__ctor"));
398     foreach(overload ; Overloads) {
399       static if (staticIndexOf!(jsonize, __traits(getAttributes, overload)) >= 0) {
400         if (canSatisfyCtor!overload(json)) {
401           return invokeCustomJsonCtor!(T, overload)(json, parent);
402         }
403       }
404     }
405 
406     // no constructor worked, is default-construction an option?
407     static if(!hasDefaultCtor!T) {
408       // not default-constructable, need to fail here
409       alias ctors = Filter!(isJsonized, __traits(getOverloads, T, "__ctor"));
410       JsonizeConstructorException.doThrow!(T, ctors)(json);
411     }
412   }
413 
414   // if no @jsonized ctor, try to use a default ctor and populate the fields
415   static if(hasDefaultCtor!T) {
416     return invokeDefaultCtor!T(json, parent, options);
417   }
418 
419   assert(0, "all attempts at deserializing " ~ fullyQualifiedName!T ~ " failed.");
420 }
421 
422 // return true if keys can satisfy parameter names
423 bool canSatisfyCtor(alias Ctor)(JSONValue json) {
424   import std.meta : aliasSeqOf;
425   auto obj = json.object;
426   alias Params   = ParameterIdentifierTuple!Ctor;
427   alias Types    = ParameterTypeTuple!Ctor;
428   alias Defaults = ParameterDefaultValueTuple!Ctor;
429   foreach(i ; aliasSeqOf!(Params.length.iota)) {
430     if (Params[i] !in obj && typeid(Defaults[i]) == typeid(void)) {
431       return false; // param had no default value and was not specified
432     }
433   }
434   return true;
435 }
436 
437 T invokeCustomJsonCtor(T, alias Ctor, P)(JSONValue json, P parent) {
438   import std.meta : aliasSeqOf;
439   enum params    = ParameterIdentifierTuple!(Ctor);
440   alias defaults = ParameterDefaultValueTuple!(Ctor);
441   alias Types    = ParameterTypeTuple!(Ctor);
442   Tuple!(Types) args;
443   foreach(i ; aliasSeqOf!(params.length.iota)) {
444     enum paramName = params[i];
445     if (paramName in json.object) {
446       args[i] = json.object[paramName].fromJSON!(Types[i]);
447     }
448     else { // no value specified in json
449       static if (is(defaults[i] == void)) {
450         enforce(0, "parameter " ~ paramName ~ " has no default value and was not specified");
451       }
452       else {
453         args[i] = defaults[i];
454       }
455     }
456   }
457   static if (isNested!T)
458     return parent..new T(args.expand);
459   else static if (is(T == class))
460     return new T(args.expand);
461   else
462     return T(args.expand);
463 }
464 
465 T invokeDefaultCtor(T, P)(JSONValue json, P parent, JsonizeOptions options) {
466   T obj;
467 
468   static if (isNested!T) {
469     obj = parent..new T;
470   }
471   else static if (is(T == struct)) {
472     obj = T.init;
473   }
474   else {
475     obj = new T;
476   }
477 
478   populate(obj, json, options);
479   return obj;
480 }
481 
482 alias isJsonized(alias member) = hasUDA!(member, jsonize);
483 
484 unittest {
485   static class Foo {
486     @jsonize int i;
487     @jsonize("s") string _s;
488     @jsonize @property string sprop() { return _s; }
489     @jsonize @property void sprop(string str) { _s = str; }
490     float f;
491     @property int iprop() { return i; }
492 
493     @jsonize this(string s) { }
494     this(int i) { }
495   }
496 
497   Foo f;
498   static assert(isJsonized!(__traits(getMember, f, "i")));
499   static assert(isJsonized!(__traits(getMember, f, "_s")));
500   static assert(isJsonized!(__traits(getMember, f, "sprop")));
501   static assert(isJsonized!(__traits(getMember, f, "i")));
502   static assert(!isJsonized!(__traits(getMember, f, "f")));
503   static assert(!isJsonized!(__traits(getMember, f, "iprop")));
504 
505   import std.typetuple : Filter;
506   static assert(Filter!(isJsonized, __traits(getOverloads, Foo, "__ctor")).length == 1);
507 }
508 
509 T invokePrimitiveCtor(T, P)(JSONValue json, P parent) {
510   static if (__traits(hasMember, T, "__ctor")) {
511     foreach(overload ; __traits(getOverloads, T, "__ctor")) {
512       alias Types = ParameterTypeTuple!overload;
513 
514       // look for an @jsonized ctor with a single parameter
515       static if (hasUDA!(overload, jsonize) && Types.length == 1) {
516         return construct!T(parent, json.fromJSON!(Types[0]));
517       }
518     }
519   }
520 
521   assert(0, "No primitive ctor for type " ~ T.stringof);
522 }
523 
524 void populate(T)(ref T obj, JSONValue json, in JsonizeOptions opt) {
525   string[] missingKeys;
526   uint fieldsFound = 0;
527 
528   foreach (member ; T._membersWithUDA!jsonize) {
529     string key = jsonKey!(T, member);
530 
531     auto required = JsonizeIn.unspecified;
532     foreach (attr ; T._getUDAs!(member, jsonize))
533       if (attr.perform_in != JsonizeIn.unspecified)
534         required = attr.perform_in;
535 
536     if (required == JsonizeIn.no) continue;
537 
538     if (auto jsonval = key in json.object) {
539       ++fieldsFound;
540       alias MemberType = T._writeMemberType!member;
541 
542       static if (!is(MemberType == void)) {
543         static if (isAggregateType!MemberType && isNested!MemberType)
544           auto val = fromJSONImpl!Inner(*jsonval, obj, opt);
545         else {
546           auto val = fromJSON!MemberType(*jsonval, opt);
547         }
548 
549         obj._writeMember!(MemberType, member)(val);
550       }
551     }
552     else {
553       if (required == JsonizeIn.yes) missingKeys ~= key;
554     }
555   }
556 
557   string[] extraKeys;
558   if (!T._jsonizeIgnoreExtra && fieldsFound < json.object.length)
559     extraKeys = json.object.keys.filter!(x => x.isUnknownKey!T).array;
560 
561   if (missingKeys.length > 0 || extraKeys.length > 0)
562     throw new JsonizeMismatchException(typeid(T), extraKeys, missingKeys);
563 }
564 
565 bool isUnknownKey(T)(string key) {
566   foreach (member ; T._membersWithUDA!jsonize)
567     if (jsonKey!(T, member) == key)
568       return false;
569 
570   return true;
571 }
572 
573 T construct(T, P, Params ...)(P parent, Params params) {
574   static if (!is(P == typeof(null))) {
575     return parent..new T(params);
576   }
577   else static if (is(typeof(T(params)) == T)) {
578     return T(params);
579   }
580   else static if (is(typeof(new T(params)) == T)) {
581     return new T(params);
582   }
583   else {
584     static assert(0, "Cannot construct");
585   }
586 }
587 
588 unittest {
589   static struct Foo {
590     this(int i) { this.i = i; }
591     int i;
592   }
593 
594   assert(construct!Foo(null).i == 0);
595   assert(construct!Foo(null, 4).i == 4);
596   assert(!__traits(compiles, construct!Foo("asd")));
597 }
598 
599 unittest {
600   static class Foo {
601     this(int i) { this.i = i; }
602 
603     this(int i, string s) {
604       this.i = i;
605       this.s = s;
606     }
607 
608     int i;
609     string s;
610   }
611 
612   assert(construct!Foo(null, 4).i == 4);
613   assert(construct!Foo(null, 4, "asdf").s == "asdf");
614   assert(!__traits(compiles, construct!Foo("asd")));
615 }
616 
617 unittest {
618   class Foo {
619     class Bar {
620       int i;
621       this(int i) { this.i = i; }
622     }
623   }
624 
625   auto f = new Foo;
626   assert(construct!(Foo.Bar)(f, 2).i == 2);
627 }
628 
629 template hasDefaultCtor(T) {
630   static if (isNested!T) {
631     alias P = typeof(__traits(parent, T).init);
632     enum hasDefaultCtor = is(typeof(P.init..new T()) == T);
633   }
634   else {
635     enum hasDefaultCtor = is(typeof(T()) == T) || is(typeof(new T()) == T);
636   }
637 }
638 
639 version(unittest) {
640   struct S1 { }
641   struct S2 { this(int i) { } }
642   struct S3 { @disable this(); }
643 
644   class C1 { }
645   class C2 { this(string s) { } }
646   class C3 { class Inner { } }
647   class C4 { class Inner { this(int i); } }
648 }
649 
650 unittest {
651   static assert( hasDefaultCtor!S1);
652   static assert( hasDefaultCtor!S2);
653   static assert(!hasDefaultCtor!S3);
654 
655   static assert( hasDefaultCtor!C1);
656   static assert(!hasDefaultCtor!C2);
657 
658   static assert( hasDefaultCtor!C3);
659   static assert( hasDefaultCtor!(C3.Inner));
660   static assert(!hasDefaultCtor!(C4.Inner));
661 }
662 
663 template hasCustomJsonCtor(T) {
664   static if (__traits(hasMember, T, "__ctor")) {
665     alias Overloads = AliasSeq!(__traits(getOverloads, T, "__ctor"));
666 
667     enum test(alias fn) = staticIndexOf!(jsonize, __traits(getAttributes, fn)) >= 0;
668 
669     enum hasCustomJsonCtor = anySatisfy!(test, Overloads);
670   }
671   else {
672     enum hasCustomJsonCtor = false;
673   }
674 }
675 
676 unittest {
677   static struct S1 { }
678   static struct S2 { this(int i); }
679   static struct S3 { @jsonize this(int i); }
680   static struct S4 { this(float f); @jsonize this(int i); }
681 
682   static assert(!hasCustomJsonCtor!S1);
683   static assert(!hasCustomJsonCtor!S2);
684   static assert( hasCustomJsonCtor!S3);
685   static assert( hasCustomJsonCtor!S4);
686 
687   static class C1 { }
688   static class C2 { this() {} }
689   static class C3 { @jsonize this() {} }
690   static class C4 { @jsonize this(int i); this(float f); }
691 
692   static assert(!hasCustomJsonCtor!C1);
693   static assert(!hasCustomJsonCtor!C2);
694   static assert( hasCustomJsonCtor!C3);
695   static assert( hasCustomJsonCtor!C4);
696 }