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(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 }