1 /*
2  * Hunt - A refined core library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.text.JsonHelper;
13 
14 import std.algorithm : map;
15 import std.array;
16 import std.conv;
17 import std.datetime;
18 import std.json;
19 import std.stdio;
20 import std.traits;
21 // import std.typecons;
22 
23 import hunt.logging;
24 // import hunt.util.serialize;
25 
26 final class JsonHelper {
27 
28     static T getItemAs(T, bool canThrow = false)(ref const(JSONValue) json, string name, 
29         T defaultValue = T.init) if (!is(T == void)) {
30         JSONType jt = json.type();
31         if (jt != JSON_TYPE.OBJECT) {            
32             return handleException!(T, canThrow)(json, "wrong member type", defaultValue);
33         }
34 
35         auto item = name in json;
36         if (item is null) {            
37             return handleException!(T, canThrow)(json, "wrong member type", defaultValue);
38         }
39         else {
40             return getAs!T(*item); // , defaultValue
41         }
42     }
43 
44     /**
45     Converts a `JSONValue` to an object of type `T` by filling its fields with the JSON's fields.
46     */
47     static T getAs(T, bool canThrow = false)(auto ref const(JSONValue) json, 
48         T defaultValue = T.init) if ((is(T == class) || is(T == struct)) && !is(T == SysTime)) {
49         JSONType jt = json.type();
50 
51         if (jt != JSON_TYPE.OBJECT) {
52             return handleException!(T, canThrow)(json, "wrong object type", defaultValue);
53         }
54 
55         static if (is(T == class)) {
56             auto result = new T();
57         }
58         else {
59             auto result = T();
60         }
61 
62         foreach (member; __traits(allMembers, T)) {
63             static if (__traits(getProtection, __traits(getMember, T,
64                     member)) == "public" && !isType!(__traits(getMember, T,
65                     member)) && !isSomeFunction!(__traits(getMember, T, member))) {
66                 try {
67                     __traits(getMember, result, member) = getAs!(typeof(__traits(getMember,
68                             result, member)), canThrow)(json[member]);
69                 }
70                 catch (JSONException e) {
71                     return handleException!(T, canThrow)(json, e.msg, defaultValue);
72                 }
73             }
74         }
75 
76         return result;
77     }
78 
79     static T getAs(T, bool canThrow = false)(auto ref const(JSONValue) json, 
80         T defaultValue = T.init) if(is(T == SysTime)) {
81   
82         JSONType jt = json.type();
83         if(jt == JSON_TYPE..string) {
84             return SysTime.fromSimpleString(json.str);
85         } else if(jt == JSON_TYPE.INTEGER) {
86             return SysTime(json.integer);  // STD time
87         } else {
88             return handleException!(T, canThrow)(json, "wrong SysTime type", defaultValue);
89         }
90     }
91 
92     // static N getAs(N : Nullable!T, T, bool canThrow = false)(auto ref const(JSONValue) json) {
93 
94     //     return (json.type == JSON_TYPE.NULL) ? N() : getAs!T(json).nullable;
95     // }
96 
97     static T getAs(T : JSONValue, bool canThrow = false)(auto ref const(JSONValue) json) {
98         import std.typecons : nullable;
99         return json.nullable;
100     }
101 
102     static T getAs(T, bool canThrow = false)(auto ref const(JSONValue) json, T defaultValue = T.init) 
103         if (isNumeric!T || isSomeChar!T) {
104 
105         switch (json.type) {
106         case JSON_TYPE.NULL, JSON_TYPE.FALSE:
107             return 0.to!T;
108 
109         case JSON_TYPE.TRUE:
110             return 1.to!T;
111 
112         case JSON_TYPE.FLOAT:
113             return json.floating.to!T;
114 
115         case JSON_TYPE.INTEGER:
116             return json.integer.to!T;
117 
118         case JSON_TYPE.UINTEGER:
119             return json.uinteger.to!T;
120 
121         case JSON_TYPE.STRING:
122             try {
123                 return json.str.to!T;
124             } catch(Exception ex) {
125                 return handleException!(T, canThrow)(json, ex.msg, defaultValue);
126             }
127 
128         default:
129             return handleException!(T, canThrow)(json, "", defaultValue);
130         }
131     }
132 
133     static T handleException(T, bool canThrow = false) (auto ref const(JSONValue) json, 
134         string message, T defaultValue = T.init) {
135         static if (canThrow) {
136             throw new JSONException(json.toString() ~ " is not a " ~ T.stringof ~ " type");
137         } else {
138         version (HUNT_DEBUG)
139             warningf(" %s is not a %s type. Using the defaults instead! \n Exception: %s",
140                 json.toString(), T.stringof, message);
141             return defaultValue;
142         }
143     }
144 
145     static T getAs(T, bool canThrow = false)(auto ref const(JSONValue) json) if (isBoolean!T) {
146         import std.json : JSON_TYPE;
147 
148         switch (json.type) {
149         case JSON_TYPE.NULL, JSON_TYPE.FALSE:
150             return false;
151 
152         case JSON_TYPE.FLOAT:
153             return json.floating != 0;
154 
155         case JSON_TYPE.INTEGER:
156             return json.integer != 0;
157 
158         case JSON_TYPE.UINTEGER:
159             return json.uinteger != 0;
160 
161         case JSON_TYPE.STRING:
162             return json.str.length > 0;
163 
164         default:
165             return true;
166         }
167     }
168 
169     static T getAs(T, bool canThrow = false)(auto ref const(JSONValue) json, T defaultValue = T.init)
170             if (isSomeString!T || is(T : string) || is(T : wstring) || is(T : dstring)) {
171 
172         static if (is(T == enum)) {
173             foreach (member; __traits(allMembers, T)) {
174                 auto m = __traits(getMember, T, member);
175 
176                 if (json.str == m) {
177                     return m;
178                 }
179             }
180             return handleException!(T, canThrow)(json, 
181                 " is not a member of " ~ typeid(T).toString(), defaultValue);
182         } else {
183             return (json.type == JSON_TYPE.STRING ? json.str : json.toString()).to!T;
184         }
185     }
186 
187     static T getAs(T : U[], bool canThrow = false, U)(auto ref const(JSONValue) json, T defaultValue = T.init)
188             if (isArray!T && !isSomeString!T && !is(T : string) && !is(T
189                 : wstring) && !is(T : dstring)) {
190 
191         switch (json.type) {
192         case JSON_TYPE.NULL:
193             return [];
194 
195         case JSON_TYPE.FALSE:
196             return [getAs!U(JSONValue(false))];
197 
198         case JSON_TYPE.TRUE:
199             return [getAs!U(JSONValue(true))];
200 
201         case JSON_TYPE.ARRAY:
202             return json.array
203                 .map!(value => getAs!U(value))
204                 .array
205                 .to!T;
206 
207         case JSON_TYPE.OBJECT:
208             // throw new JSONException(json.toString() ~ " is not a string type");
209             return handleException!(T, canThrow)(json, "", defaultValue);
210 
211         default:
212             return [getAs!U(json)];
213         }
214     }
215 
216     static T getAs(T : U[K], bool canThrow = false, U, K)(auto ref const(JSONValue) json, T defaultValue = T.init) 
217         if (isAssociativeArray!T) {
218         U[K] result;
219 
220         switch (json.type) {
221         case JSON_TYPE.NULL:
222             return result;
223 
224         case JSON_TYPE.OBJECT:
225             foreach (key, value; json.object) {
226                 result[key.to!K] = getAs!U(value);
227             }
228 
229             break;
230 
231         case JSON_TYPE.ARRAY:
232             foreach (key, value; json.array) {
233                 result[key.to!K] = getAs!U(value);
234             }
235 
236             break;
237 
238         default:
239             // throw new JSONException(json.toString() ~ " is not an object type");
240             return handleException!(T, canThrow)(json, "", defaultValue);
241         }
242 
243         return result;
244     }
245 
246     /// toJson
247 
248     // Nullable!JSONValue toJson(T)(T value) {
249         // return JSONValue.init;
250     //     return toJson(t, level, ignore);
251     // }
252 
253     static JSONValue toJson(T)(T value, uint level = uint.max, bool ignore = true)
254             if ((is(T == class) || is(T == struct)) && !is(T == JSONValue) && !is(T == SysTime)) {
255         import std.traits : isSomeFunction, isType;
256         // import std.typecons : nullable;
257 
258         static if (is(T == class)) {
259             if (value is null) {
260                 return JSONValue(null);
261             }
262         }
263 
264         auto result = JSONValue();
265 
266         foreach (member; __traits(allMembers, T)) {
267             static if (__traits(getProtection, __traits(getMember, T,
268                     member)) == "public" && !isType!(__traits(getMember, T,
269                     member)) && !isSomeFunction!(__traits(getMember, T, member))) {
270                 auto json = toJson!(typeof(__traits(getMember, value, member)))(
271                         __traits(getMember, value, member));
272 
273                 if (!json.isNull) {
274                     result[member] = json;
275                 }
276             }
277         }
278 
279         return result;
280     }
281 
282 
283     static JSONValue toJson(T)(T value, bool asInteger=true) if(is(T == SysTime)) {
284         if(asInteger)
285             return JSONValue(value.stdTime()); // STD time
286         else 
287             return JSONValue(value.toString());
288     }
289 
290     // static Nullable!JSONValue toJson(N : Nullable!T, T)(N value) {
291     //     return value.isNull ? Nullable!JSONValue() : Nullable!JSONValue(toJson!T(value.get()));
292     // }
293 
294     static JSONValue toJson(T)(T value)
295             if ((!is(T == class) && !is(T == struct)) || is(T == JSONValue)) {
296         return JSONValue(value);
297     }
298 
299     static JSONValue toJson(T : U[], U)(T value)
300             if (isArray!T && !isSomeString!T && !is(T : string) && !is(T : wstring) && !is(T : dstring)) {
301         import std.algorithm : map;
302 
303         return JSONValue(value.map!(item => toJson(item))()
304                 .map!(json => json.isNull ? JSONValue(null) : json).array);
305     }
306 
307     static JSONValue toJson(T : U[K], U, K)(T value) if (isAssociativeArray!T) {
308         auto result = JSONValue();
309 
310         foreach (key; value.keys) {
311             auto json = toJson(value[key]);
312             result[key.to!string] = json.isNull ? JSONValue(null) : json;
313         }
314 
315         return result;
316     }
317 
318 }