1 /** 2 Utilities releated to JSON processing. 3 */ 4 module dcord.util.json; 5 6 import std.conv, 7 std.meta, 8 std.traits, 9 std.stdio; 10 11 public import vibe.data.json : VibeJSON = Json, parseJsonString; 12 13 import dcord.types.base : IModel; 14 public import dcord.util..string : camelCaseToUnderscores; 15 16 17 enum JSONIgnore; 18 enum JSONFlat; 19 enum JSONTimestamp; 20 21 struct JSONSource { 22 string src; 23 } 24 25 struct JSONListToMap { 26 string field; 27 } 28 29 VibeJSON serializeToJSON(T)(T sourceObj, string[] ignoredFields = []) { 30 import std.algorithm : canFind; 31 32 version (JSON_DEBUG_S) { 33 pragma(msg, "Generating Serialization for: ", typeof(sourceObj)); 34 } 35 36 VibeJSON result = VibeJSON.emptyObject; 37 string sourceFieldName, dstFieldName; 38 39 foreach (fieldName; FieldNameTuple!T) { 40 // Runtime check if we are being ignored 41 if (ignoredFields.canFind(fieldName)) continue; 42 43 version(JSON_DEBUG_S) { 44 pragma(msg, " -> ", fieldName); 45 } 46 47 alias FieldType = typeof(__traits(getMember, sourceObj, fieldName)); 48 49 static if (hasUDA!(mixin("sourceObj." ~ fieldName), JSONIgnore)) { 50 version (JSON_DEBUG) { 51 pragma(msg, " -> skipping"); 52 writefln(" -> skipping"); 53 } 54 continue; 55 } else static if ((is(FieldType == struct) || is(FieldType == class)) && 56 hasUDA!(typeof(mixin("sourceObj." ~ fieldName)), JSONIgnore)) { 57 version (JSON_DEBUG) { 58 pragma(msg, " -> skipping"); 59 writefln(" -> skipping"); 60 } 61 continue; 62 } else static if (fieldName[0] == '_') { 63 version (JSON_DEBUG) { 64 pragma(msg, " -> skipping"); 65 writefln(" -> skipping"); 66 } 67 continue; 68 } else { 69 static if (hasUDA!(mixin("sourceObj." ~ fieldName), JSONSource)) { 70 dstFieldName = getUDAs!(mixin("sourceObj." ~ fieldName), JSONSource)[0].src; 71 } else { 72 dstFieldName = camelCaseToUnderscores(fieldName); 73 } 74 75 76 static if (hasUDA!(mixin("sourceObj." ~ fieldName), JSONListToMap)) { 77 version (JSON_DEBUG) pragma(msg, " -= TODO"); 78 // TODO 79 /+ 80 __traits(getMember, sourceObj, fieldName) = typeof(__traits(getMember, sourceObj, fieldName)).fromJSONArray!( 81 getUDAs!(mixin("sourceObj." ~ fieldName), JSONListToMap)[0].field 82 )(sourceObj, fieldData); 83 +/ 84 } else { 85 version (JSON_DEBUG) pragma(msg, " -= dumpSingleField"); 86 result[dstFieldName] = dumpSingleField(mixin("sourceObj." ~ fieldName)); 87 } 88 } 89 } 90 91 return result; 92 } 93 94 private VibeJSON dumpSingleField(T)(ref T field) { 95 static if (is(T == struct)) { 96 return field.serializeToJSON; 97 } else static if (is(T == class)) { 98 return field ? field.serializeToJSON : VibeJSON(null); 99 } else static if (isSomeString!T) { 100 return VibeJSON(field); 101 } else static if (isArray!T) { 102 return VibeJSON(); 103 // TODO 104 } else { 105 return VibeJSON(field); 106 } 107 } 108 109 void deserializeFromJSON(T)(T sourceObj, VibeJSON sourceData) { 110 version (JSON_DEBUG) { 111 pragma(msg, "Generating Deserialization for: ", typeof(sourceObj)); 112 } 113 114 string sourceFieldName, dstFieldName; 115 VibeJSON fieldData; 116 117 foreach (fieldName; FieldNameTuple!T) { 118 version (JSON_DEBUG) { 119 pragma(msg, " -> ", fieldName); 120 writefln("%s", fieldName); 121 } 122 123 alias FieldType = typeof(__traits(getMember, sourceObj, fieldName)); 124 125 // First we need to check whether we should ignore this field 126 static if (hasUDA!(mixin("sourceObj." ~ fieldName), JSONIgnore)) { 127 version (JSON_DEBUG) { 128 pragma(msg, " -> skipping"); 129 writefln(" -> skipping"); 130 } 131 continue; 132 } else static if ((is(FieldType == struct) || is(FieldType == class)) && 133 hasUDA!(typeof(mixin("sourceObj." ~ fieldName)), JSONIgnore)) { 134 version (JSON_DEBUG) { 135 pragma(msg, " -> skipping"); 136 writefln(" -> skipping"); 137 } 138 continue; 139 } else static if (fieldName[0] == '_') { 140 version (JSON_DEBUG) { 141 pragma(msg, " -> skipping"); 142 writefln(" -> skipping"); 143 } 144 continue; 145 } else { 146 // Now we grab the data 147 static if (hasUDA!(mixin("sourceObj." ~ fieldName), JSONFlat)) { 148 fieldData = sourceData; 149 } else { 150 static if (hasUDA!(mixin("sourceObj." ~ fieldName), JSONSource)) { 151 sourceFieldName = getUDAs!(mixin("sourceObj." ~ fieldName), JSONSource)[0].src; 152 } else { 153 sourceFieldName = camelCaseToUnderscores(fieldName); 154 } 155 156 if ( 157 (sourceFieldName !in sourceData) || 158 (sourceData[sourceFieldName].type == VibeJSON.Type.undefined) || 159 (sourceData[sourceFieldName].type == VibeJSON.Type.null_)) { 160 continue; 161 } 162 163 fieldData = sourceData[sourceFieldName]; 164 } 165 166 // Now we parse the data 167 version (JSON_DEBUG) { 168 writefln(" -> src from %s", fieldData); 169 } 170 171 // meh 172 static if (hasUDA!(mixin("sourceObj." ~ fieldName), JSONListToMap)) { 173 version (JSON_DEBUG) pragma(msg, " -= JSONListToMap"); 174 __traits(getMember, sourceObj, fieldName) = typeof(__traits(getMember, sourceObj, fieldName)).fromJSONArray!( 175 getUDAs!(mixin("sourceObj." ~ fieldName), JSONListToMap)[0].field 176 )(sourceObj, fieldData); 177 } else static if (hasUDA!(mixin("sourceObj." ~ fieldName), JSONTimestamp)) { 178 version (JSON_DEBUG) pragma(msg, " -= loadTimestampField"); 179 __traits(getMember, sourceObj, fieldName) = loadTimestampField!(T, FieldType)(sourceObj, fieldData); 180 } else { 181 version (JSON_DEBUG) pragma(msg, " -= loadSingleField"); 182 __traits(getMember, sourceObj, fieldName) = loadSingleField!(T, FieldType)(sourceObj, fieldData); 183 } 184 } 185 } 186 } 187 188 template ArrayElementType(T : T[]) { 189 alias T ArrayElementType; 190 } 191 192 template AATypes(T) { 193 alias ArrayElementType!(typeof(T.keys)) key; 194 alias ArrayElementType!(typeof(T.values)) value; 195 } 196 197 private DateT loadTimestampField(T, DateT)(T sourceObj, VibeJSON data) { 198 return DateT.fromISOExtString(data.to!string); 199 } 200 201 private Z loadSingleField(T, Z)(T sourceObj, VibeJSON data) { 202 version (JSON_DEBUG) { 203 writefln(" -> parsing type %s from %s", fullyQualifiedName!Z, data.type); 204 } 205 206 // Some deserialization strategies we take require a reference to the type. 207 Z result; 208 209 static if (is(Z == VibeJSON)) { 210 return data; 211 } else static if (is(Z == struct)) { 212 result.deserializeFromJSON(data); 213 return result; 214 } else static if (is(Z == enum)) { 215 // Read the stored type and then cast it to our enum type 216 return cast(Z)data.to!(OriginalType!Z); 217 } else static if (is(Z == class)) { 218 // If we have a constructor which allows the parent object and the JSON data use it 219 static if (__traits(compiles, { 220 new Z(sourceObj, data); 221 })) { 222 result = new Z(sourceObj, data); 223 result.attach(sourceObj); 224 } else static if (hasMember!(Z, "client")) { 225 result = new Z(__traits(getMember, sourceObj, "client"), data); 226 result.attach(sourceObj); 227 } else { 228 result = new Z; 229 result.deserializeFromJSON(data); 230 } 231 return result; 232 } else static if (isSomeString!Z) { 233 static if (__traits(compiles, { 234 return cast(Z)data.get!string; 235 })) { 236 return cast(Z)data.get!string; 237 } else { 238 return data.get!string.to!Z; 239 } 240 } else static if (isArray!Z) { 241 alias AT = ArrayElementType!(Z); 242 243 foreach (obj; data) { 244 AT v = loadSingleField!(T, AT)(sourceObj, obj); 245 result ~= v; 246 } 247 return result; 248 } else static if (isAssociativeArray!Z) { 249 alias ArrayElementType!(typeof(result.keys)) Tk; 250 alias ArrayElementType!(typeof(result.values)) Tv; 251 252 foreach (ref string k, ref v; data) { 253 Tv val = loadSingleField!(T, Tv)(sourceObj, v); 254 255 result[k.to!Tk] = val; 256 } 257 return result; 258 } else static if (isIntegral!Z) { 259 if (data.type == VibeJSON.Type..string) { 260 return data.get!string.to!Z; 261 } else { 262 static if (__traits(compiles, { result = data.to!Z; })) { 263 return data.to!Z; 264 } else { 265 return data.get!Z; 266 } 267 } 268 } else { 269 return data.to!Z; 270 } 271 } 272 273 private void attach(T, Z)(T baseObj, Z parentObj) { 274 foreach (fieldName; FieldNameTuple!T) { 275 alias FieldType = typeof(__traits(getMember, baseObj, fieldName)); 276 277 static if (is(FieldType == Z)) { 278 __traits(getMember, baseObj, fieldName) = parentObj; 279 } 280 } 281 } 282 283 T deserializeFromJSON(T)(VibeJSON jsonData) { 284 T result = new T; 285 result.deserializeFromJSON(jsonData); 286 return result; 287 } 288 289 T[] deserializeFromJSONArray(T)(VibeJSON jsonData, T delegate(VibeJSON) cons) { 290 T[] result; 291 292 foreach (item; jsonData) { 293 result ~= cons(item); 294 } 295 296 return result; 297 } 298 299 unittest { 300 // Test Enums 301 enum TestEnum { 302 A = "A", 303 B = "B", 304 } 305 306 class TestEnumClass { 307 TestEnum test; 308 } 309 310 (new TestEnumClass()).deserializeFromJSON( 311 parseJsonString(q{{"test": "A"}}), 312 ); 313 314 // Test Timestamp 315 import std.datetime : SysTime; 316 317 class TestTimestampClass { 318 @JSONTimestamp 319 SysTime timestamp; 320 } 321 322 (new TestTimestampClass()).deserializeFromJSON( 323 parseJsonString(q{{"timestamp": "2018-11-13T02:51:57.736000+00:00"}}), 324 ); 325 }