1 /**
2 * Copyright © Yurai Web Framework 2021
3 * License: MIT (https://github.com/YuraiWeb/yurai/blob/main/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 */
6 module yurai.json.jsonobject;
7 
8 import std.traits : isSomeString, isScalarType;
9 import std.string : format, strip;
10 import std.array : replace, join, array;
11 import std.conv : to;
12 import std.algorithm : map, sort, group;
13 
14 import yurai.core.conv;
15 
16 import yurai.json.jsonobjectmember;
17 import yurai.json.jsontype;
18 
19 final class Json(S)
20 if (isSomeString!S)
21 {
22   private:
23   alias JsonObject = Json!S;
24   alias JsonMapMember = JsonObjectMember!S;
25 
26   JsonMapMember[S] _members;
27   JsonObject[] _items;
28   S _text;
29   double _number;
30   bool _booleanValue;
31   JsonType _jsonType;
32 
33   S escapeJsonString(S text)
34   {
35     return text
36       .replace("\t", "\\t")
37       .replace("\b", "\\b")
38       .replace("\r", "\\r")
39       .replace("\n", "\\n")
40       .replace("\f", "\\f");
41   }
42 
43   public:
44   this()
45   {
46     _jsonType = JsonType.jsonNull;
47   }
48 
49   bool addMember(S key, JsonObject member)
50   {
51     auto obj = new JsonMapMember(key, _members ? _members.length : 0, member);
52     _members[key] = obj;
53     _jsonType = JsonType.jsonObject;
54     return true;
55   }
56 
57   bool getMember(S key, out JsonObject member)
58   {
59     if (_jsonType == JsonType.jsonNull)
60     {
61       member = null;
62       return false;
63     }
64 
65     if (_jsonType != JsonType.jsonObject)
66     {
67       member = null;
68       return false;
69     }
70 
71     if (!_members)
72     {
73       member = null;
74       return false;
75     }
76 
77     auto entry = _members.get(key, null);
78 
79     if (entry)
80     {
81       member = cast(JsonObject)entry.obj;
82     }
83     else
84     {
85       member = null;
86     }
87 
88     return member !is null;
89   }
90 
91   bool getMembers(out JsonObject[S] members)
92   {
93     if (_jsonType == JsonType.jsonNull)
94     {
95       members = typeof(members).init;
96       return false;
97     }
98 
99     if (_jsonType != JsonType.jsonObject)
100     {
101       members = typeof(members).init;
102       return false;
103     }
104 
105     foreach (k,v; _members)
106     {
107       members[k] = v.obj;
108     }
109 
110     return true;
111   }
112 
113   bool addItem(JsonObject item)
114   {
115     _items ~= item;
116     _jsonType = JsonType.jsonArray;
117     return true;
118   }
119 
120   bool getItem(size_t index, out JsonObject item)
121   {
122     if (_jsonType == JsonType.jsonNull)
123     {
124       item = null;
125       return false;
126     }
127 
128     if (_jsonType != JsonType.jsonArray)
129     {
130       item = null;
131       return false;
132     }
133 
134     if (!_items || _items.length <= index)
135     {
136       item = null;
137       return false;
138     }
139 
140     item = _items[index];
141 
142     return item !is null;
143   }
144 
145   bool getItems(out JsonObject[] items)
146   {
147     if (_jsonType == JsonType.jsonNull)
148     {
149       items = null;
150       return false;
151     }
152 
153     if (_jsonType != JsonType.jsonArray)
154     {
155       items = null;
156       return false;
157     }
158 
159     items = _items ? _items : [];
160     return true;
161   }
162 
163   bool setText(S text)
164   {
165     _text = text;
166     _jsonType = JsonType.jsonString;
167     return true;
168   }
169 
170   bool getText(out S text)
171   {
172     if (_jsonType == JsonType.jsonNull)
173     {
174       text = null;
175       return false;
176     }
177 
178     if (_jsonType != JsonType.jsonString)
179     {
180       text = null;
181       return false;
182     }
183 
184     text = _text;
185     return true;
186   }
187 
188   bool setNumber(T)(T number)
189   if (isScalarType!T)
190   {
191     double numberValue;
192     if (!tryParse(number, numberValue))
193     {
194       return false;
195     }
196 
197     _number = numberValue;
198     _jsonType = JsonType.jsonNumber;
199     return true;
200   }
201 
202   bool getNumber(T = double)(out T number)
203   if (isScalarType!T)
204   {
205     if (_jsonType == JsonType.jsonNull)
206     {
207       number = T.init;
208       return false;
209     }
210 
211     if (_jsonType != JsonType.jsonNumber)
212     {
213       number = T.init;
214       return false;
215     }
216 
217     static if (is(T == double))
218     {
219       number = _number;
220       return true;
221     }
222     else
223     {
224       return tryParse(_number, number);
225     }
226   }
227 
228   bool setBoolean(bool booleanValue)
229   {
230     _booleanValue = booleanValue;
231     _jsonType = JsonType.jsonBoolean;
232     return true;
233   }
234 
235   bool getBoolean(out bool value)
236   {
237     if (_jsonType == JsonType.jsonNull)
238     {
239       value = false;
240       return false;
241     }
242 
243     if (_jsonType != JsonType.jsonBoolean)
244     {
245       value = false;
246       return false;
247     }
248 
249     value = _booleanValue;
250 
251     return true;
252   }
253 
254   @property
255   {
256     JsonType jsonType() { return _jsonType; }
257 
258     void jsonType(JsonType newJsonType)
259     {
260       _jsonType = newJsonType;
261     }
262   }
263 
264   JsonObject opIndex(S key)
265   {
266     JsonObject obj;
267     if (getMember(key, obj))
268     {
269       return obj;
270     }
271 
272     return null;
273   }
274 
275   JsonObject opIndex(size_t index)
276   {
277     JsonObject obj;
278     if (getItem(index, obj))
279     {
280       return obj;
281     }
282 
283     return null;
284   }
285 
286   S toPrettyString(size_t tabCount = 0, bool initTab = true)
287   {
288     S tabs = "";
289     foreach (_; 0 .. tabCount)
290     {
291       tabs ~= "\t";
292     }
293 
294     S memberTabs = "";
295     foreach (_; 0 .. tabCount + 1)
296     {
297       memberTabs ~= "\t";
298     }
299 
300     switch (jsonType)
301     {
302       case JsonType.jsonNull: return "null";
303 
304       case JsonType.jsonBoolean: return _booleanValue.to!S;
305 
306       case JsonType.jsonNumber: return _number.to!S;
307 
308       case jsonType.jsonString: return "\"%s\"".format(escapeJsonString(_text)); // TODO: Escape newlines etc.
309 
310       case JsonType.jsonObject:
311         S obj = (initTab ? tabs : "") ~ "{";
312         bool hasMembers = false;
313 
314         if (_members)
315         {
316           auto sortedMembers = _members ? _members.values.sort.group.map!(g => g[0]).array : [];
317 
318           S memberStr = join(sortedMembers.map!(m => memberTabs ~ `"%s": %s`.format(m.key, m.obj.toPrettyString(tabCount + 1, false))).array, ",\r\n");
319 
320           hasMembers = memberStr.strip.length > 0;
321 
322           if (hasMembers)
323           {
324             obj ~= "\r\n";
325             obj ~= memberStr;
326             obj ~= "\r\n";
327           }
328         }
329 
330         return obj ~ (hasMembers ? tabs : "") ~ "}";
331 
332       case JsonType.jsonArray:
333         S arr = (initTab ? tabs : "") ~ "[";
334         bool hasItems = false;
335 
336         if (_items)
337         {
338           S itemStr = join(_items.map!(i => memberTabs ~ i.toPrettyString(tabCount + 1, false)).array, ",\r\n");
339 
340           hasItems = itemStr.strip.length > 0;
341 
342           if (hasItems)
343           {
344             arr ~= "\r\n";
345             arr ~= itemStr;
346             arr ~= "\r\n";
347           }
348         }
349 
350         return arr ~ (hasItems ? tabs : "") ~ "]";
351 
352       default: return "undefined";
353     }
354   }
355 
356   private
357   {
358     S toStringImpl()
359     {
360       switch (jsonType)
361       {
362         case JsonType.jsonNull: return "null";
363 
364         case JsonType.jsonBoolean: return _booleanValue.to!S;
365 
366         case JsonType.jsonNumber: return _number.to!S;
367 
368         case jsonType.jsonString: return "\"%s\"".format(escapeJsonString(_text)); // TODO: Escape newlines etc.
369 
370         case JsonType.jsonObject:
371           S obj = "{";
372 
373           if (_members)
374           {
375             auto sortedMembers = _members ? _members.values.sort.group.map!(g => g[0]).array : [];
376 
377             obj ~= join(sortedMembers.map!(m => `"%s":%s`.format(m.key, m.obj.toString())).array, ",");
378           }
379 
380           return obj ~ "}";
381 
382         case JsonType.jsonArray:
383           S arr = "[";
384 
385           if (_items)
386           {
387             arr ~= join(_items.map!(i => i.toString).array, ",");
388           }
389 
390           return arr ~ "]";
391 
392         default: return "undefined";
393       }
394     }
395   }
396 
397   override string toString()
398   {
399     return toStringImpl.to!string;
400   }
401 }