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 }