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 }