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.common     : jsonize;
521   import jsonizer.exceptions : JsonizeConstructorException;
522   import jsonizer.fromjson   : fromJSON;
523   import jsonizer.jsonize    : JsonizeMe;
524 
525   static class A {
526     mixin JsonizeMe;
527     private const int a;
528 
529     this(float f) {
530       a = 0;
531     }
532 
533     @jsonize this(int a) {
534       this.a = a;
535     }
536 
537     @jsonize this(string s, float f) {
538       a = 0;
539     }
540   }
541 
542   auto ex = collectException!JsonizeConstructorException(`{}`.parseJSON.fromJSON!A);
543   assert(ex !is null, "failure to match @jsonize'd constructors should throw");
544   assert(ex.msg.canFind("(int a)") && ex.msg.canFind("(string s, float f)"),
545     "JsonizeConstructorException message should contain attempted constructors");
546   assert(!ex.msg.canFind("(float f)"),
547     "JsonizeConstructorException message should not contain non-jsonized constructors");
548 }
549 
550 // Validate issue #17:
551 // Unable to construct class containing private (not marked with @jsonize) types.
552 unittest {
553   import std.json : parseJSON;
554   import jsonizer;
555 
556   static class A {
557     mixin JsonizeMe;
558 
559     private int a;
560 
561     @jsonize public this(int a) {
562         this.a = a;
563     }
564   }
565 
566   auto json = `{ "a": 5}`.parseJSON;
567   auto a = fromJSON!A(json);
568 
569   assert(a.a == 5);
570 }
571 
572 // Validate issue #18:
573 // Unable to construct class with const types.
574 unittest {
575   import std.json : parseJSON;
576   import jsonizer;
577 
578   static class A {
579     mixin JsonizeMe;
580 
581     const int a;
582 
583     @jsonize public this(int a) {
584         this.a = a;
585     }
586   }
587 
588   auto json = `{ "a": 5}`.parseJSON;
589   auto a = fromJSON!A(json);
590 
591   assert(a.a == 5);
592 }
593 
594 // Validate issue #19:
595 // Unable to construct class containing private (not marked with @jsonize) types.
596 unittest {
597   import std.json : parseJSON;
598   import jsonizer;
599 
600   static class A {
601     mixin JsonizeMe;
602 
603     alias Integer = int;
604     Integer a;
605 
606     @jsonize public this(Integer a) {
607         this.a = a;
608     }
609   }
610 
611   auto json = `{ "a": 5}`.parseJSON;
612   auto a = fromJSON!A(json);
613 
614   assert(a.a == 5);
615 }
616 
617 unittest {
618   import std.json : parseJSON;
619   import jsonizer;
620 
621   static struct A
622   {
623     mixin JsonizeMe;
624     @jsonize int a;
625     @jsonize(Jsonize.opt) string attr;
626     @jsonize(JsonizeIn.opt) string attr2;
627   }
628 
629   auto a = A(5);
630   assert(a == a.toJSON.fromJSON!A);
631   assert(a.toJSON == `{ "a":5, "attr2":"" }`.parseJSON);
632   assert(a.toJSON != `{ "a":5, "attr":"", "attr2":"" }`.parseJSON);
633   a.attr = "hello";
634   assert(a == a.toJSON.fromJSON!A);
635   assert(a.toJSON == `{ "a":5, "attr":"hello", "attr2":"" }`.parseJSON);
636   a.attr2 = "world";
637   assert(a == a.toJSON.fromJSON!A);
638   assert(a.toJSON == `{ "a":5, "attr":"hello", "attr2":"world" }`.parseJSON);
639   a.attr = "";
640   assert(a == a.toJSON.fromJSON!A);
641   assert(a.toJSON == `{ "a":5, "attr2":"world" }`.parseJSON);
642 }
643 
644 unittest {
645   import std.json : parseJSON;
646   import jsonizer;
647 
648   static struct A
649   {
650     mixin JsonizeMe;
651     @jsonize int a;
652     @disable int opEquals( ref const(A) );
653   }
654 
655   static assert(!is(typeof(A.init==A.init)));
656 
657   static struct B
658   {
659     mixin JsonizeMe;
660     @jsonize(Jsonize.opt) A a;
661   }
662 
663   auto b = B(A(10));
664   assert(b.a.a == 10);
665   assert(b.a.a == (b.toJSON.fromJSON!B).a.a);
666   assert(b.toJSON == `{"a":{"a":10}}`.parseJSON);
667   b.a.a = 0;
668   assert(b.a.a == int.init );
669   assert(b.a.a == (b.toJSON.fromJSON!B).a.a);
670   assert(b.toJSON == "{}".parseJSON);
671 }
672 
673 unittest {
674   import std.json : parseJSON;
675   import std.exception;
676   import jsonizer;
677 
678   static struct T
679   {
680     mixin JsonizeMe;
681     @jsonize(Jsonize.opt)
682     {
683       int a;
684       @jsonize(JsonizeOut.no,JsonizeIn.yes) string b;
685       @jsonize(JsonizeOut.yes,JsonizeIn.no) string c;
686     }
687   }
688 
689   auto t = T(5);
690   assertThrown(t.toJSON.fromJSON!T);
691   assert(t.toJSON == `{ "a":5, "c":"" }`.parseJSON);
692   t.b = "hello";
693   assert(t == `{ "a":5, "b":"hello" }`.parseJSON.fromJSON!T);
694   t.c = "world";
695   assert(t.toJSON == `{ "a":5, "c":"world" }`.parseJSON);
696   t.a = 0;
697   assert(t.toJSON == `{ "c":"world" }`.parseJSON);
698   auto t2 = `{ "b":"hello", "c":"okda" }`.parseJSON.fromJSON!T;
699   assert(t.a == t2.a);
700   assert(t.b == t2.b);
701   assert(t2.c == "");
702 }
703 
704 // issue 29: Can't have static fields in JsonizeMe type; doesn't compile
705 unittest {
706   import std.json : parseJSON;
707   static class A {
708     mixin JsonizeMe;
709     static string s;
710     static string str() { return "s"; }
711     @jsonize int a;
712   }
713   auto a = new A;
714   a.a = 5;
715   assert(a.toJSON == `{ "a":5 }`.parseJSON);
716 }