1 /// `extract!T` converts a `JSONValue` to an instance of `T` 2 module internal.extract; 3 4 import std.json; 5 import std.conv; 6 import std.range; 7 import std.traits; 8 import std.string; 9 import std.algorithm; 10 import std.exception; 11 import std.typetuple; 12 import std.typecons : staticIota; 13 import internal.attribute; 14 15 /// json member used to map a json object to a D type 16 enum jsonizeClassKeyword = "class"; 17 18 private void enforceJsonType(T)(JSONValue json, JSON_TYPE[] expected ...) { 19 enum fmt = "extract!%s expected json type to be one of %s but got json type %s. json input: %s"; 20 enforce(expected.canFind(json.type), format(fmt, typeid(T), expected, json.type, json)); 21 } 22 23 /// extract a boolean from a json value 24 T extract(T : bool)(JSONValue json) { 25 if (json.type == JSON_TYPE.TRUE) { 26 return true; 27 } 28 else if (json.type == JSON_TYPE.FALSE) { 29 return false; 30 } 31 else { 32 enforce(0, format("tried to extract bool from json of type %s", json.type)); 33 } 34 } 35 36 /// extract a string type from a json value 37 T extract(T : string)(JSONValue json) { 38 if (json.type == JSON_TYPE.NULL) { return null; } 39 enforceJsonType!T(json, JSON_TYPE.STRING); 40 return cast(T) json.str; 41 } 42 43 /// extract a numeric type from a json value 44 T extract(T : real)(JSONValue json) if (!is(T == enum)) { 45 switch(json.type) { 46 case JSON_TYPE.FLOAT: 47 return cast(T) json.floating; 48 case JSON_TYPE.INTEGER: 49 return cast(T) json.integer; 50 case JSON_TYPE.UINTEGER: 51 return cast(T) json.uinteger; 52 case JSON_TYPE.STRING: 53 enforce(json.str.isNumeric, format("tried to extract %s from json string %s", T.stringof, json.str)); 54 return to!T(json.str); // try to parse string as int 55 default: 56 enforce(0, format("tried to extract %s from json of type %s", T.stringof, json.type)); 57 } 58 assert(0, "should not be reacheable"); 59 } 60 61 /// extract an enumerated type from a json value 62 T extract(T)(JSONValue json) if (is(T == enum)) { 63 enforceJsonType!T(json, JSON_TYPE.STRING); 64 return to!T(json.str); 65 } 66 67 /// extract an array from a JSONValue 68 T extract(T)(JSONValue json) if (isArray!T && !isSomeString!(T)) { 69 if (json.type == JSON_TYPE.NULL) { return T.init; } 70 enforceJsonType!T(json, JSON_TYPE.ARRAY); 71 alias ElementType = ForeachType!T; 72 T vals; 73 foreach(idx, val ; json.array) { 74 static if (isStaticArray!T) { 75 vals[idx] = val.extract!ElementType; 76 } 77 else { 78 vals ~= val.extract!ElementType; 79 } 80 } 81 return vals; 82 } 83 84 /// extract an associative array from a JSONValue 85 T extract(T)(JSONValue json) if (isAssociativeArray!T) { 86 assert(is(KeyType!T : string), "toJSON requires string keys for associative array"); 87 if (json.type == JSON_TYPE.NULL) { return null; } 88 enforceJsonType!T(json, JSON_TYPE.OBJECT); 89 alias ValType = ValueType!T; 90 T map; 91 foreach(key, val ; json.object) { 92 map[key] = extract!ValType(val); 93 } 94 return map; 95 } 96 97 /// extract a value from a json object by its key 98 T extract(T)(JSONValue json, string key) { 99 enforceJsonType!T(json, JSON_TYPE.OBJECT); 100 enforce(key in json.object, "tried to extract non-existent key " ~ key ~ " from JSONValue"); 101 return extract!T(json.object[key]); 102 } 103 104 /// extract a value from a json object by its key, return defaultVal if key not found 105 T extract(T)(JSONValue json, string key, T defaultVal) { 106 enforceJsonType!T(json, JSON_TYPE.OBJECT); 107 return (key in json.object) ? extract!T(json.object[key]) : defaultVal; 108 } 109 110 /// extract a user-defined class or struct from a JSONValue 111 T extract(T)(JSONValue json) if (!isBuiltinType!T) { 112 static if (is(T == class)) { 113 if (json.type == JSON_TYPE.NULL) { return null; } 114 } 115 enforceJsonType!T(json, JSON_TYPE.OBJECT); 116 117 static if (is(typeof(null) : T)) { 118 // look for class keyword in json 119 auto className = json.extract!string(jsonizeClassKeyword, null); 120 // try creating an instance with Object.factory 121 if (className !is null) { 122 auto obj = Object.factory(className); 123 assert(obj !is null, "failed to Object.factory " ~ className); 124 auto instance = cast(T) obj; 125 assert(instance !is null, "failed to cast " ~ className ~ " to " ~ T.stringof); 126 instance.populateFromJSON(json); 127 return instance; 128 } 129 } 130 131 // next, try to find a contructor marked with @jsonize and call that 132 static if (__traits(hasMember, T, "__ctor")) { 133 alias Overloads = TypeTuple!(__traits(getOverloads, T, "__ctor")); 134 foreach(overload ; Overloads) { 135 if (staticIndexOf!(jsonize, __traits(getAttributes, overload)) >= 0 && 136 canSatisfyCtor!overload(json)) { 137 return invokeCustomJsonCtor!(T, overload)(json); 138 } 139 } 140 } 141 142 // if no @jsonized ctor, try to use a default ctor and populate the fields 143 static if(is(T == struct) || is(typeof(new T) == T)) { // can object be default constructed? 144 return invokeDefaultCtor!(T)(json); 145 } 146 assert(0, T.stringof ~ " must have a no-args constructor to support extract"); 147 } 148 149 /// return true if keys can satisfy parameter names 150 private bool canSatisfyCtor(alias Ctor)(JSONValue json) { 151 auto obj = json.object; 152 alias Params = ParameterIdentifierTuple!Ctor; 153 alias Types = ParameterTypeTuple!Ctor; 154 alias Defaults = ParameterDefaultValueTuple!Ctor; 155 foreach(i ; staticIota!(0, Params.length)) { 156 if (Params[i] !in obj && typeid(Defaults[i]) == typeid(void)) { 157 return false; // param had no default value and was not specified 158 } 159 } 160 return true; 161 } 162 163 private T invokeCustomJsonCtor(T, alias Ctor)(JSONValue json) { 164 enum params = ParameterIdentifierTuple!(Ctor); 165 alias defaults = ParameterDefaultValueTuple!(Ctor); 166 alias Types = ParameterTypeTuple!(Ctor); 167 Tuple!(Types) args; 168 foreach(i ; staticIota!(0, params.length)) { 169 enum paramName = params[i]; 170 if (paramName in json.object) { 171 args[i] = json.object[paramName].extract!(Types[i]); 172 } 173 else { // no value specified in json 174 static if (is(defaults[i] == void)) { 175 enforce(0, "parameter " ~ paramName ~ " has no default value and was not specified"); 176 } 177 else { 178 args[i] = defaults[i]; 179 } 180 } 181 } 182 static if (is(T == class)) { 183 return new T(args.expand); 184 } 185 else { 186 return T(args.expand); 187 } 188 } 189 190 private T invokeDefaultCtor(T)(JSONValue json) { 191 T obj; 192 static if (is(T == struct)) { 193 obj = T.init; 194 } 195 else { 196 obj = new T; 197 } 198 obj.populateFromJSON(json); 199 return obj; 200 } 201 202 bool hasCustomJsonCtor(T)() { 203 static if (__traits(hasMember, T, "__ctor")) { 204 alias Overloads = TypeTuple!(__traits(getOverloads, T, "__ctor")); 205 foreach(overload ; Overloads) { 206 static if (staticIndexOf!(jsonize, __traits(getAttributes, overload)) >= 0) { 207 return true; 208 } 209 } 210 } 211 return false; 212 }