1 /**
2   * Import all public modules for jsonizer.
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;
10 
11 public import jsonizer.tojson;
12 public import jsonizer.fromjson;
13 public import jsonizer.jsonize;
14 public import jsonizer.common;
15 public import jsonizer.exceptions;
16 
17 /// object serialization -- fields only
18 unittest {
19   import std.math : approxEqual;
20 
21   static class Fields {
22     // classes must have a no-args constructor
23     // or a constructor marked with @jsonize (see later example)
24     this() { }
25 
26     this(int iVal, float fVal, string sVal, int[] aVal, string noJson) {
27       i = iVal;
28       f = fVal;
29       s = sVal;
30       a = aVal;
31       dontJsonMe = noJson;
32     }
33 
34     mixin JsonizeMe;
35 
36     @jsonize { // fields to jsonize -- test different access levels
37       public int i;
38       protected float f;
39       public int[] a;
40       private string s;
41     }
42     string dontJsonMe;
43 
44     override bool opEquals(Object o) {
45       auto other = cast(Fields) o;
46       return i == other.i && s == other.s && a == other.a && f.approxEqual(other.f);
47     }
48   }
49 
50   auto obj = new Fields(1, 4.2, "tally ho!", [9, 8, 7, 6], "blarg");
51   auto json = toJSON!Fields(obj);
52 
53   assert(json.object["i"].integer == 1);
54   assert(json.object["f"].floating.approxEqual(4.2));
55   assert(json.object["s"].str == "tally ho!");
56   assert(json.object["a"].array[0].integer == 9);
57   assert("dontJsonMe" !in json.object);
58 
59   // reconstruct from json
60   auto r = fromJSON!Fields(json);
61   assert(r.i == 1);
62   assert(r.f.approxEqual(4.2));
63   assert(r.s == "tally ho!");
64   assert(r.a == [9, 8, 7, 6]);
65   assert(r.dontJsonMe is null);
66 
67   // array of objects
68   auto a = [
69     new Fields(1, 4.2, "tally ho!", [9, 8, 7, 6], "blarg"),
70     new Fields(7, 42.2, "yea merrily", [1, 4, 6, 4], "asparagus")
71   ];
72 
73   // serialize array of user objects to json
74   auto jsonArray = toJSON!(Fields[])(a);
75   // reconstruct from json
76   assert(fromJSON!(Fields[])(jsonArray) == a);
77 }
78 
79 /// object serialization with properties
80 unittest {
81   import std.math : approxEqual;
82 
83   static class Props {
84     this() { } // class must have a no-args ctor
85 
86     this(int iVal, float fVal, string sVal, string noJson) {
87       _i = iVal;
88       _f = fVal;
89       _s = sVal;
90       _dontJsonMe = noJson;
91     }
92 
93     mixin JsonizeMe;
94 
95     @property {
96       // jsonize ref property accessing private field
97       @jsonize ref int i() { return _i; }
98       // jsonize property with non-trivial get/set methods
99       @jsonize float f() { return _f - 3; } // the jsonized value will equal _f - 3
100       float f(float val) { return _f = val + 5; } // 5 will be added to _f when retrieving from json
101       // don't jsonize these properties
102       ref string s() { return _s; }
103       ref string dontJsonMe() { return _dontJsonMe; }
104     }
105 
106     private:
107     int _i;
108     float _f;
109     @jsonize string _s;
110     string _dontJsonMe;
111   }
112 
113   auto obj = new Props(1, 4.2, "tally ho!", "blarg");
114   auto json = toJSON(obj);
115 
116   assert(json.object["i"].integer == 1);
117   assert(json.object["f"].floating.approxEqual(4.2 - 3.0)); // property should have subtracted 3 on retrieval
118   assert(json.object["_s"].str == "tally ho!");
119   assert("dontJsonMe" !in json.object);
120 
121   auto r = fromJSON!Props(json);
122   assert(r.i == 1);
123   assert(r._f.approxEqual(4.2 - 3.0 + 5.0)); // property accessor should add 5
124   assert(r._s == "tally ho!");
125   assert(r.dontJsonMe is null);
126 }
127 
128 /// object serialization with custom constructor
129 unittest {
130   import std.conv : to;
131   import std.json : parseJSON;
132   import std.math : approxEqual;
133   import jsonizer.tojson : toJSON;
134 
135   static class Custom {
136     mixin JsonizeMe;
137 
138     this(int i) {
139       _i = i;
140       _s = "something";
141       _f = 10.2;
142     }
143 
144     @jsonize this(int _i, string _s, float _f = 20.2) {
145       this._i = _i;
146       this._s = _s ~ " jsonized";
147       this._f = _f;
148     }
149 
150     @jsonize this(double d) { // alternate ctor
151       _f = d.to!float;
152       _s = d.to!string;
153       _i = d.to!int;
154     }
155 
156     private:
157     @jsonize {
158       string _s;
159       float _f;
160       int _i;
161     }
162   }
163 
164   auto c = new Custom(12);
165   auto json = toJSON(c);
166   assert(json.object["_i"].integer == 12);
167   assert(json.object["_s"].str == "something");
168   assert(json.object["_f"].floating.approxEqual(10.2));
169   auto c2 = fromJSON!Custom(json);
170   assert(c2._i == 12);
171   assert(c2._s == "something jsonized");
172   assert(c2._f.approxEqual(10.2));
173 
174   // test alternate ctor
175   json = parseJSON(`{"d" : 5}`);
176   c = json.fromJSON!Custom;
177   assert(c._f.approxEqual(5) && c._i == 5 && c._s == "5");
178 }
179 
180 /// struct serialization
181 unittest {
182   import std.math : approxEqual;
183 
184   static struct S {
185     mixin JsonizeMe;
186 
187     @jsonize {
188       int x;
189       float f;
190       string s;
191     }
192     int dontJsonMe;
193 
194     this(int x, float f, string s, int noJson) {
195       this.x = x;
196       this.f = f;
197       this.s = s;
198       this.dontJsonMe = noJson;
199     }
200   }
201 
202   auto s = S(5, 4.2, "bogus", 7);
203   auto json = toJSON(s); // serialize a struct
204 
205   assert(json.object["x"].integer == 5);
206   assert(json.object["f"].floating.approxEqual(4.2));
207   assert(json.object["s"].str == "bogus");
208   assert("dontJsonMe" !in json.object);
209 
210   auto r = fromJSON!S(json);
211   assert(r.x == 5);
212   assert(r.f.approxEqual(4.2));
213   assert(r.s == "bogus");
214   assert(r.dontJsonMe == int.init);
215 }
216 
217 /// json file I/O
218 unittest {
219   import std.file          : remove;
220   import jsonizer.fromjson : readJSON;
221   import jsonizer.tojson   : writeJSON;
222 
223   enum file = "test.json";
224   scope(exit) remove(file);
225 
226   static struct Data {
227     mixin JsonizeMe;
228 
229     @jsonize {
230       int x;
231       string s;
232       float f;
233     }
234   }
235 
236   // write an array of user-defined structs
237   auto array = [Data(5, "preposterous", 12.7), Data(8, "tesseract", -2.7), Data(5, "baby sloths", 102.7)];
238   file.writeJSON(array);
239   auto readBack = file.readJSON!(Data[]);
240   assert(readBack == array);
241 
242   // now try an associative array
243   auto aa = ["alpha": Data(27, "yams", 0), "gamma": Data(88, "spork", -99.999)];
244   file.writeJSON(aa);
245   auto aaReadBack = file.readJSON!(Data[string]);
246   assert(aaReadBack == aa);
247 }
248 
249 /// inheritance
250 unittest {
251   import std.math : approxEqual;
252   static class Parent {
253     mixin JsonizeMe;
254     @jsonize int x;
255     @jsonize string s;
256   }
257 
258   static class Child : Parent {
259     mixin JsonizeMe;
260     @jsonize float f;
261   }
262 
263   auto c = new Child;
264   c.x = 5;
265   c.s = "hello";
266   c.f = 2.1;
267 
268   auto json = c.toJSON;
269   assert(json.fromJSON!int("x") == 5);
270   assert(json.fromJSON!string("s") == "hello");
271   assert(json.fromJSON!float("f").approxEqual(2.1));
272 
273   auto child = json.fromJSON!Child;
274   assert(child !is null);
275   assert(child.x == 5 && child.s == "hello" && child.f.approxEqual(2.1));
276 
277   auto parent = json.fromJSON!Parent;
278   assert(parent.x == 5 && parent.s == "hello");
279 }
280 
281 /// inheritance with  ctors
282 unittest {
283   import std.math : approxEqual;
284   static class Parent {
285     mixin JsonizeMe;
286 
287     @jsonize this(int x, string s) {
288       _x = x;
289       _s = s;
290     }
291 
292     @jsonize @property {
293       int x()    { return _x; }
294       string s() { return _s; }
295     }
296 
297     private:
298     int _x;
299     string _s;
300   }
301 
302   static class Child : Parent {
303     mixin JsonizeMe;
304 
305     @jsonize this(int x, string s, float f) {
306       super(x, s);
307       _f = f;
308     }
309 
310     @jsonize @property {
311       float f() { return _f; }
312     }
313 
314     private:
315     float _f;
316   }
317 
318   auto c = new Child(5, "hello", 2.1);
319 
320   auto json = c.toJSON;
321   assert(json.fromJSON!int("x") == 5);
322   assert(json.fromJSON!string("s") == "hello");
323   assert(json.fromJSON!float("f").approxEqual(2.1));
324 
325   auto child = json.fromJSON!Child;
326   assert(child.x == 5 && child.s == "hello" && child.f.approxEqual(2.1));
327 
328   auto parent = json.fromJSON!Parent;
329   assert(parent.x == 5 && parent.s == "hello");
330 }
331 
332 /// renamed members
333 unittest {
334   static class Bleh {
335     mixin JsonizeMe;
336     private {
337       @jsonize("x") int _x;
338       @jsonize("s") string _s;
339     }
340   }
341 
342   auto b = new Bleh;
343   b._x = 5;
344   b._s = "blah";
345 
346   auto json = b.toJSON;
347 
348   assert(json.fromJSON!int("x") == 5);
349   assert(json.fromJSON!string("s") == "blah");
350 
351   auto reconstruct = json.fromJSON!Bleh;
352   assert(reconstruct._x == b._x && reconstruct._s == b._s);
353 }
354 
355 /// type inference
356 unittest {
357   import std.json   : parseJSON;
358   import std.string : format;
359   import std.traits : fullyQualifiedName;
360 
361   static class TestComponent {
362     mixin JsonizeMe;
363     @jsonize int c;
364   }
365 
366   static class TestCompA : TestComponent {
367     mixin JsonizeMe;
368     @jsonize int a;
369   }
370 
371   static class TestCompB : TestComponent {
372     mixin JsonizeMe;
373     @jsonize string b;
374   }
375 
376   string classKeyA = fullyQualifiedName!TestCompA;
377   string classKeyB = fullyQualifiedName!TestCompB;
378 
379   auto data = `[
380     {
381       "class": "%s",
382       "c": 1,
383       "a": 5
384     },
385     {
386       "class": "%s",
387       "c": 2,
388       "b": "hello"
389     }
390   ]`.format(classKeyA, classKeyB).parseJSON.fromJSON!(TestComponent[]);
391 
392   auto a = cast(TestCompA) data[0];
393   auto b = cast(TestCompB) data[1];
394 
395   assert(a !is null && a.c == 1 && a.a == 5);
396   assert(b !is null && b.c == 2 && b.b == "hello");
397 }
398 
399 /// type inference with custom type key
400 unittest {
401   import std.string : format;
402   import std.traits : fullyQualifiedName;
403 
404   static class TestComponent {
405     mixin JsonizeMe;
406     @jsonize int c;
407   }
408 
409   static class TestCompA : TestComponent {
410     mixin JsonizeMe;
411     @jsonize int a;
412   }
413 
414   static class TestCompB : TestComponent {
415     mixin JsonizeMe;
416     @jsonize string b;
417   }
418 
419   // use "type" instead of "class" to identify dynamic type
420   JsonizeOptions options;
421   options.classKey = "type";
422 
423   // need to use these because unittest is assigned weird name
424   // normally would just be "modulename.classname"
425   string classKeyA = fullyQualifiedName!TestCompA;
426   string classKeyB = fullyQualifiedName!TestCompB;
427 
428   auto data = `[
429     {
430       "type": "%s",
431       "c": 1,
432       "a": 5
433     },
434     {
435       "type": "%s",
436       "c": 2,
437       "b": "hello"
438     }
439   ]`.format(classKeyA, classKeyB)
440   .fromJSONString!(TestComponent[])(options);
441 
442   auto a = cast(TestCompA) data[0];
443   auto b = cast(TestCompB) data[1];
444   assert(a !is null && a.c == 1 && a.a == 5);
445   assert(b !is null && b.c == 2 && b.b == "hello");
446 }
447 
448 //test the class map
449 unittest {
450   import std.string : format;
451   import std.traits : fullyQualifiedName;
452 
453   static class TestComponent {
454     mixin JsonizeMe;
455     @jsonize int c;
456   }
457 
458   static class TestCompA : TestComponent {
459     mixin JsonizeMe;
460     @jsonize int a;
461   }
462 
463   static class TestCompB : TestComponent {
464     mixin JsonizeMe;
465     @jsonize string b;
466   }
467 
468   // use "type" instead of "class" to identify dynamic type
469   JsonizeOptions options;
470   options.classKey = "type";
471 
472   const string wrongName = "unrelated";
473 
474   string[string] classMap = [
475     TestCompA.stringof : fullyQualifiedName!TestCompA,
476     TestCompB.stringof : fullyQualifiedName!TestCompB,
477     wrongName          : fullyQualifiedName!TestCompA
478   ];
479 
480   options.classMap = delegate string(string rawKey) {
481     if(auto val = rawKey in classMap)
482       return *val;
483     else
484       return null;
485   };
486 
487   auto data = `[
488     {
489       "type": "%s",
490       "c": 1,
491       "a": 5
492     },
493     {
494       "type": "%s",
495       "c": 2,
496       "b": "hello"
497     },
498     {
499       "type": "%s",
500       "c": 3,
501       "a": 12
502     }
503   ]`.format(TestCompA.stringof, TestCompB.stringof, wrongName)
504   .fromJSONString!(TestComponent[])(options);
505 
506   auto a = cast(TestCompA) data[0];
507   auto b = cast(TestCompB) data[1];
508   auto c = cast(TestCompA) data[2];
509   assert(a !is null && a.c == 1 && a.a == 5);
510   assert(b !is null && b.c == 2 && b.b == "hello");
511   assert(c !is null && c.c == 3 && c.a == 12);
512 }
513 
514 // Validate issue #20:
515 // Unable to de-jsonize a class when a construct is marked @jsonize.
516 unittest {
517   import std.json            : parseJSON;
518   import std.algorithm       : canFind;
519   import std.exception       : collectException;
520   import jsonizer.jsonize    : jsonize, JsonizeMe;
521   import jsonizer.exceptions : JsonizeConstructorException;
522   import jsonizer.fromjson   : fromJSON;
523 
524   static class A {
525     mixin JsonizeMe;
526     private const int a;
527 
528     this(float f) {
529       a = 0;
530     }
531 
532     @jsonize this(int a) {
533       this.a = a;
534     }
535 
536     @jsonize this(string s, float f) {
537       a = 0;
538     }
539   }
540 
541   auto ex = collectException!JsonizeConstructorException(`{}`.parseJSON.fromJSON!A);
542   assert(ex !is null, "failure to match @jsonize'd constructors should throw");
543   assert(ex.msg.canFind("(int a)") && ex.msg.canFind("(string s, float f)"),
544     "JsonizeConstructorException message should contain attempted constructors");
545   assert(!ex.msg.canFind("(float f)"),
546     "JsonizeConstructorException message should not contain non-jsonized constructors");
547 }
548 
549 // Validate issue #17:
550 // Unable to construct class containing private (not marked with @jsonize) types.
551 unittest {
552   import std.json : parseJSON;
553   import jsonizer;
554 
555   static class A {
556     mixin JsonizeMe;
557 
558     private int a;
559 
560     @jsonize public this(int a) {
561         this.a = a;
562     }
563   }
564 
565   auto json = `{ "a": 5}`.parseJSON;
566   auto a = fromJSON!A(json);
567 
568   assert(a.a == 5);
569 }
570 
571 // Validate issue #18:
572 // Unable to construct class with const types.
573 unittest {
574   import std.json : parseJSON;
575   import jsonizer;
576 
577   static class A {
578     mixin JsonizeMe;
579 
580     const int a;
581 
582     @jsonize public this(int a) {
583         this.a = a;
584     }
585   }
586 
587   auto json = `{ "a": 5}`.parseJSON;
588   auto a = fromJSON!A(json);
589 
590   assert(a.a == 5);
591 }
592 
593 // Validate issue #19:
594 // Unable to construct class containing private (not marked with @jsonize) types.
595 unittest {
596   import std.json : parseJSON;
597   import jsonizer;
598 
599   static class A {
600     mixin JsonizeMe;
601 
602     alias Integer = int;
603     Integer a;
604 
605     @jsonize public this(Integer a) {
606         this.a = a;
607     }
608   }
609 
610   auto json = `{ "a": 5}`.parseJSON;
611   auto a = fromJSON!A(json);
612 
613   assert(a.a == 5);
614 }
615 
616 unittest {
617   import std.json : parseJSON;
618   import jsonizer;
619 
620   static struct A
621   {
622     mixin JsonizeMe;
623     @jsonize int a;
624     @jsonize(Jsonize.opt) string attr;
625     @jsonize(JsonizeIn.opt) string attr2;
626   }
627 
628   auto a = A(5);
629   assert(a == a.toJSON.fromJSON!A);
630   assert(a.toJSON == `{ "a":5, "attr2":"" }`.parseJSON);
631   assert(a.toJSON != `{ "a":5, "attr":"", "attr2":"" }`.parseJSON);
632   a.attr = "hello";
633   assert(a == a.toJSON.fromJSON!A);
634   assert(a.toJSON == `{ "a":5, "attr":"hello", "attr2":"" }`.parseJSON);
635   a.attr2 = "world";
636   assert(a == a.toJSON.fromJSON!A);
637   assert(a.toJSON == `{ "a":5, "attr":"hello", "attr2":"world" }`.parseJSON);
638   a.attr = "";
639   assert(a == a.toJSON.fromJSON!A);
640   assert(a.toJSON == `{ "a":5, "attr2":"world" }`.parseJSON);
641 }
642 
643 unittest {
644   import std.json : parseJSON;
645   import jsonizer;
646 
647   static struct A
648   {
649     mixin JsonizeMe;
650     @jsonize int a;
651     @disable int opEquals( ref const(A) );
652   }
653 
654   static assert(!is(typeof(A.init==A.init)));
655 
656   static struct B
657   {
658     mixin JsonizeMe;
659     @jsonize(Jsonize.opt) A a;
660   }
661 
662   auto b = B(A(10));
663   assert(b.a.a == 10);
664   assert(b.a.a == (b.toJSON.fromJSON!B).a.a);
665   assert(b.toJSON == `{"a":{"a":10}}`.parseJSON);
666   b.a.a = 0;
667   assert(b.a.a == int.init );
668   assert(b.a.a == (b.toJSON.fromJSON!B).a.a);
669   assert(b.toJSON == "{}".parseJSON);
670 }
671 
672 unittest {
673   import std.json : parseJSON;
674   import std.exception;
675   import jsonizer;
676 
677   static struct T
678   {
679     mixin JsonizeMe;
680     @jsonize(Jsonize.opt)
681     {
682       int a;
683       @jsonize(JsonizeOut.no,JsonizeIn.yes) string b;
684       @jsonize(JsonizeOut.yes,JsonizeIn.no) string c;
685     }
686   }
687 
688   auto t = T(5);
689   assertThrown(t.toJSON.fromJSON!T);
690   assert(t.toJSON == `{ "a":5, "c":"" }`.parseJSON);
691   t.b = "hello";
692   assert(t == `{ "a":5, "b":"hello" }`.parseJSON.fromJSON!T);
693   t.c = "world";
694   assert(t.toJSON == `{ "a":5, "c":"world" }`.parseJSON);
695   t.a = 0;
696   assert(t.toJSON == `{ "c":"world" }`.parseJSON);
697   auto t2 = `{ "b":"hello", "c":"okda" }`.parseJSON.fromJSON!T;
698   assert(t.a == t2.a);
699   assert(t.b == t2.b);
700   assert(t2.c == "");
701 }
702 
703 // issue 29: Can't have static fields in JsonizeMe type; doesn't compile
704 unittest {
705   import std.json : parseJSON;
706   static class A {
707     mixin JsonizeMe;
708     static string s;
709     static string str() { return "s"; }
710     @jsonize int a;
711   }
712   auto a = new A;
713   a.a = 5;
714   assert(a.toJSON == `{ "a":5 }`.parseJSON);
715 }