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