1 /** 2 Utilities releated to JSON processing. 3 */ 4 module dscord.util.json; 5 6 import std.stdio; 7 8 import std.conv, 9 std.meta, 10 std.traits; 11 12 public import vibe.data.json : VibeJSON = Json, parseJsonString; 13 14 import dscord.types.base : IModel; 15 public import dscord.util..string : camelCaseToUnderscores; 16 17 18 enum JSONIgnore; 19 enum JSONFlat; 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 { 178 version (JSON_DEBUG) pragma(msg, " -= loadSingleField"); 179 loadSingleField!(T, FieldType)(sourceObj, __traits(getMember, sourceObj, fieldName), fieldData); 180 } 181 } 182 } 183 } 184 185 template ArrayElementType(T : T[]) { 186 alias T ArrayElementType; 187 } 188 189 template AATypes(T) { 190 alias ArrayElementType!(typeof(T.keys)) key; 191 alias ArrayElementType!(typeof(T.values)) value; 192 } 193 194 private bool loadSingleField(T, Z)(T sourceObj, ref Z result, VibeJSON data) { 195 version (JSON_DEBUG) { 196 writefln(" -> parsing type %s from %s", fullyQualifiedName!Z, data.type); 197 } 198 199 static if (is(Z == struct)) { 200 result.deserializeFromJSON(data); 201 } else static if (is(Z == class)) { 202 // If we have a constructor which allows the parent object and the JSON data use it 203 static if (__traits(compiles, { 204 new Z(sourceObj, data); 205 })) { 206 result = new Z(sourceObj, data); 207 result.attach(sourceObj); 208 } else static if (hasMember!(Z, "client")) { 209 result = new Z(__traits(getMember, sourceObj, "client"), data); 210 result.attach(sourceObj); 211 } else { 212 result = new Z; 213 result.deserializeFromJSON(data); 214 } 215 } else static if (isSomeString!Z) { 216 static if (__traits(compiles, { 217 result = cast(Z)data.get!string; 218 })) { 219 result = cast(Z)data.get!string; 220 } else { 221 result = data.get!string.to!Z; 222 } 223 } else static if (isArray!Z) { 224 alias AT = ArrayElementType!(Z); 225 226 foreach (obj; data) { 227 AT v; 228 loadSingleField!(T, AT)(sourceObj, v, obj); 229 result ~= v; 230 } 231 } else static if (isAssociativeArray!Z) { 232 alias ArrayElementType!(typeof(result.keys)) Tk; 233 alias ArrayElementType!(typeof(result.values)) Tv; 234 235 foreach (ref string k, ref v; data) { 236 Tv val; 237 238 loadSingleField!(T, Tv)(sourceObj, val, v); 239 240 result[k.to!Tk] = val; 241 } 242 } else static if (isIntegral!Z) { 243 if (data.type == VibeJSON.Type..string) { 244 result = data.get!string.to!Z; 245 } else { 246 static if (__traits(compiles, { result = data.to!Z; })) { 247 result = data.to!Z; 248 } else { 249 result = data.get!Z; 250 } 251 } 252 } else { 253 result = data.to!Z; 254 } 255 256 return false; 257 } 258 259 private void attach(T, Z)(T baseObj, Z parentObj) { 260 foreach (fieldName; FieldNameTuple!T) { 261 alias FieldType = typeof(__traits(getMember, baseObj, fieldName)); 262 263 static if (is(FieldType == Z)) { 264 __traits(getMember, baseObj, fieldName) = parentObj; 265 } 266 } 267 } 268 269 270 T deserializeFromJSON(T)(VibeJSON jsonData) { 271 T result = new T; 272 result.deserializeFromJSON(jsonData); 273 return result; 274 } 275 276 T[] deserializeFromJSONArray(T)(VibeJSON jsonData, T delegate(VibeJSON) cons) { 277 T[] result; 278 279 foreach (item; jsonData) { 280 result ~= cons(item); 281 } 282 283 return result; 284 }