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 }