1 /**
2   * Enables marking user-defined types for JSON serialization.
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.jsonize;
10 
11 import jsonizer.common;
12 
13 /**
14  * Enable `fromJSON`/`toJSON` support for the type this is mixed in to.
15  *
16  * In order for fields to be (de)serialized, they must be annotated with
17  * `jsonize` (in addition to having the mixin within the type).
18  *
19  * This mixin will _not_ recursively apply to nested types. If a nested type is
20  * to be serialized, it must have `JsonizeMe` mixed in as well.
21  * Params:
22  *   ignoreExtra = whether to silently ignore json keys that do not map to serialized members
23  */
24 mixin template JsonizeMe(JsonizeIgnoreExtraKeys ignoreExtra = JsonizeIgnoreExtraKeys.yes) {
25   static import std.json;
26 
27   enum type = typeof(this).stringof;
28 
29   template _membersWithUDA(uda) {
30     import std.meta : Erase, Filter;
31     import std.traits : isSomeFunction, hasUDA;
32     import std.string : startsWith;
33 
34     template include(string name) {
35       // filter out inaccessible members, such as those with @disable
36       static if (__traits(compiles, mixin(type~"."~name))) {
37         enum isReserved = name.startsWith("__");
38 
39         enum isInstanceField =
40           __traits(compiles, mixin(type~"."~name~".offsetof"));
41 
42         // the &this.name check makes sure this is not an alias
43         enum isInstanceMethod =
44           __traits(compiles, mixin("&"~type~"."~name)) &&
45           isSomeFunction!(mixin(type~"."~name)) &&
46           !__traits(isStaticFunction, mixin(type~"."~name));
47 
48         static if ((isInstanceField || isInstanceMethod) && !isReserved)
49           enum include = hasUDA!(mixin(type~"."~name), uda);
50         else
51           enum include = false;
52       }
53       else
54         enum include = false;
55     }
56 
57     enum members = Erase!("this", __traits(allMembers, typeof(this)));
58     alias _membersWithUDA = Filter!(include, members);
59   }
60 
61   template _getUDAs(string name, alias uda) {
62       import std.meta : Filter;
63       import std.traits : getUDAs;
64       enum isValue(alias T) = is(typeof(T));
65       alias _getUDAs = Filter!(isValue, getUDAs!(mixin(type~"."~name), uda));
66   }
67 
68   template _writeMemberType(string name) {
69     import std.meta : Filter, AliasSeq;
70     import std.traits : Parameters;
71     alias overloads = AliasSeq!(__traits(getOverloads, typeof(this), name));
72     enum hasOneArg(alias f) = Parameters!f.length == 1;
73     alias setters = Filter!(hasOneArg, overloads);
74     void tryassign()() { mixin("this."~name~"=this."~name~";"); }
75 
76     static if (setters.length)
77       alias _writeMemberType = Parameters!(setters[0]);
78     else static if (__traits(compiles, tryassign()))
79       alias _writeMemberType = typeof(mixin(type~"."~name));
80     else
81       alias _writeMemberType = void;
82   }
83 
84   auto _readMember(string name)() {
85       return __traits(getMember, this, name);
86   }
87 
88   void _writeMember(T, string name)(T val) {
89       __traits(getMember, this, name) = val;
90   }
91 
92   static import std.json;
93   static import jsonizer.common;
94   alias _jsonizeIgnoreExtra = ignoreExtra;
95   private alias constructor =
96     typeof(this) function(std.json.JSONValue,
97                           in ref jsonizer.common.JsonizeOptions);
98   static constructor[string] _jsonizeCtors;
99 
100   static if (is(typeof(this) == class)) {
101     static this() {
102       import std.traits : BaseClassesTuple, fullyQualifiedName;
103       import jsonizer.fromjson;
104       enum name = fullyQualifiedName!(typeof(this));
105       foreach (base ; BaseClassesTuple!(typeof(this)))
106         static if (__traits(hasMember, base, "_jsonizeCtors"))
107           base._jsonizeCtors[name] = &_fromJSON!(typeof(this));
108     }
109   }
110 }
111 
112 unittest {
113   static struct attr { string s; }
114   static struct S {
115     mixin JsonizeMe;
116     @attr this(int i) { }
117     @attr this(this) { }
118     @attr ~this() { }
119     @attr int a;
120     @attr static int b;
121     @attr void c() { }
122     @attr static void d() { }
123     @attr int e(string s) { return 1; }
124     @attr static int f(string s) { return 1; }
125     @attr("foo") int g;
126     @attr("foo") static int h;
127     int i;
128     static int j;
129     void k() { };
130     static void l() { };
131     alias Int = int;
132     enum s = 5;
133   }
134 
135   static assert ([S._membersWithUDA!attr] == ["a", "c", "e", "g"]);
136 }
137 
138 unittest {
139   struct attr { string s; }
140   struct Outer {
141     mixin JsonizeMe;
142     @attr int a;
143     struct Inner {
144       mixin JsonizeMe;
145       @attr this(int i) { }
146       @attr this(this) { }
147       @attr int b;
148     }
149   }
150 
151   static assert ([Outer._membersWithUDA!attr] == ["a"]);
152   static assert ([Outer.Inner._membersWithUDA!attr] == ["b"]);
153 }
154 
155 unittest {
156   struct attr { string s; }
157   struct A {
158     mixin JsonizeMe;
159     @disable this();
160     @disable this(this);
161     @attr int a;
162   }
163 
164   static assert ([A._membersWithUDA!attr] == ["a"]);
165 }
166 
167 unittest {
168   struct attr { string s; }
169 
170   static class A {
171     mixin JsonizeMe;
172     @attr int a;
173     @attr string b() { return "hi"; }
174     string c() { return "hi"; }
175   }
176 
177   static assert ([A._membersWithUDA!attr] == ["a", "b"]);
178 
179   static class B : A { mixin JsonizeMe; }
180 
181   static assert ([B._membersWithUDA!attr] == ["a", "b"]);
182 
183   static class C : A {
184     mixin JsonizeMe;
185     @attr int d;
186   }
187 
188   static assert ([C._membersWithUDA!attr] == ["d", "a", "b"]);
189 
190   static class D : A {
191     mixin JsonizeMe;
192     @disable int a;
193   }
194 
195   static assert ([D._membersWithUDA!attr] == ["b"]);
196 }
197 
198 // Validate name conflicts (issue #36)
199 unittest {
200   static struct attr { string s; }
201   static struct S {
202     mixin JsonizeMe;
203     @attr("foo") string name, key;
204   }
205 
206   static assert([S._membersWithUDA!attr] == ["name", "key"]);
207   static assert([S._getUDAs!("name", attr)] == [attr("foo")]);
208   static assert([S._getUDAs!("key", attr)] == [attr("foo")]);
209 }
210 
211 // #40: Can't deserialize both as exact type and as part of a hierarchy
212 unittest
213 {
214   import jsonizer.fromjson;
215   import jsonizer.tojson;
216 
217   static class Base
218   {
219       mixin JsonizeMe;
220       @jsonize("class") string className() { return this.classinfo.name; }
221   }
222   static class Derived : Base
223   {
224       mixin JsonizeMe;
225   }
226 
227   auto a = new Derived();
228   auto b = a.toJSON.fromJSON!Derived;
229   assert(b !is null);
230 }