1 module uim.core.datatypes.json;
2 
3 @safe:
4 import uim.core;
5 
6 /// Checks if every key is in json object
7 bool hasAllKeys(Json json, string[] keys, bool deepSearch = false) {
8   foreach(key; keys) {
9     if (!hasKey(json, key, deepSearch)) return false; 
10   }
11   return true;
12 }
13 ///
14 unittest {
15   auto json = parseJsonString(`{"a":"b", "c":{"d":1}, "e":["f", {"g":"h"}]}`);
16   assert(json.hasAllKeys(["a", "c"]));
17   assert(json.hasAllKeys(["a", "d"], true));
18 }
19 
20 /// Check if Json has key
21 bool hasAnyKey(Json json, string[] keys, bool deepSearch = false) { 
22   foreach(key; keys) 
23     if (hasKey(json, key, deepSearch)) return true;
24   return false;
25 }
26 ///
27 unittest {
28   auto json = parseJsonString(`{"a":"b", "c":{"d":1}, "e":["f", {"g":"h"}]}`);
29   assert(json.hasAnyKey(["a"]));
30   assert(json.hasAnyKey(["d"], true));
31 }
32 
33 /// Searching key in json, if depth = true also in subnodes  
34 bool hasKey(Json json, string key, bool deepSearch = false) {
35   if (json.type == Json.Type.object) {
36     foreach(kv; json.byKeyValue) {
37       if (kv.key == key) return true;
38       if (deepSearch) {
39         auto result = kv.value.hasKey(key, deepSearch);
40         if (result) return true;
41       }
42     }
43   }
44   if (deepSearch) {
45     if (json.type == Json.Type.array) {
46       for(size_t i = 0; i < json.length; i++) {
47         const result = json[i].hasKey(key, deepSearch);
48         if (result) return true; 
49       }
50     }
51   }
52   return false;
53 }
54 ///
55 unittest {
56   auto json = parseJsonString(`{"a":"b", "c":{"d":1}, "e":["f", {"g":"h"}]}`);
57   assert(json.hasKey("a"));
58   assert(json.hasKey("d", true));
59 }
60 
61 bool hasAllValues(Json json, Json[] values, bool deepSearch = false) {
62   foreach(value; values) if (!hasValue(json, value, deepSearch)) return false;
63   return true;
64 }
65 ///
66 unittest {
67   auto json = parseJsonString(`{"a":"b", "c":{"d":1}, "e":["f", {"g":"h"}], "i":"j"}`);
68   assert(json.hasAllValues([Json("b"), Json("j")]));
69   assert(json.hasAllValues([Json("h"), Json(1)], true));
70 }
71 
72 bool hasAnyValue(Json json, Json[] values, bool deepSearch = false) {
73   foreach(value; values) if (hasValue(json, value, deepSearch)) return true;
74   return false;
75 }
76 ///
77 unittest {
78   auto json = parseJsonString(`{"a":"b", "c":{"d":1}, "e":["f", {"g":"h"}], "i":"j"}`);
79   assert(json.hasAllValues([Json("b"), Json("j")]));
80   assert(json.hasAllValues([Json("h"), Json(1)], true));
81 }
82 
83 /// Searching for value in Json
84 bool hasValue(Json json, Json value, bool deepSearch = false) {
85   if (json.type == Json.Type.object) {
86     foreach(kv; json.byKeyValue) {
87       if (kv.value == value) return true;
88       if (deepSearch) {
89         auto result = kv.value.hasValue(value, deepSearch);
90         if (result) return true;
91       }
92     }
93   }
94   if (deepSearch) {
95     if (json.type == Json.Type.array) {
96       for(size_t i = 0; i < json.length; i++) {
97         const result = json[i].hasValue(value, deepSearch);
98         if (result) return true; 
99       }
100     }
101   }
102   return false;
103 }
104 ///
105 unittest {
106   auto json = parseJsonString(`{"a":"b", "c":{"d":1}, "e":["f", {"g":"h"}]}`);
107   assert(json.hasValue(Json("b")));
108   assert(json.hasValue(Json("h"), true));
109   assert(!json.hasValue(Json("x")));
110   assert(!json.hasValue(Json("y"), true));
111 }
112 
113 /// Check if jsonPath exists
114 bool hasPath(Json json, string path) {
115   if (json.type != Json.Type.object) return false;
116 
117   auto items = path.split("/");
118   if (items.length > 1) return hasPath(json, items[1..$]);
119   return false;
120 }
121 ///
122 unittest {
123   auto json = parseJsonString(`{"a":"b", "c":{"d":1}, "e":["f", {"g":"h"}]}`);
124   assert(json.hasPath("/c/d"));
125 }
126 
127 /// Check if jsonPath items exists
128 bool hasPath(Json json, string[] pathItems) {
129   if (json.type != Json.Type.object) return false;
130 
131   auto j = json;
132   foreach(pathItem; pathItems) {
133     if (pathItem in j)  {
134       if (pathItems.length > 1) return hasPath(j[pathItem], pathItems[1..$]); 
135       else return true; 
136     }
137     else return false;
138   }
139   return true;
140 }
141 ///
142 unittest {
143   auto json = parseJsonString(`{"a":"b", "c":{"d":1}, "e":["f", {"g":"h"}]}`);
144   assert(json.hasPath(["c", "d"]));
145 }
146 
147 /// Reduce Json Object to keys (remove others)
148 Json reduceKeys(Json json, string[] keys) {
149   if (json.type == Json.Type.object) {
150     Json result = Json.emptyObject;
151     foreach(key; keys) if (json.hasKey(key)) result[key] = json[key];
152     return result;
153   }
154   return Json(null); // Not object or keys
155 }
156 unittest {
157   version(uim_core) {     /// TODO
158     writeln("test uim_core");
159   }
160 }
161 
162 /// Remove keys from Json Object
163 Json removeKeys(Json json, string[] delKeys...) { return removeKeys(json, delKeys); }
164 Json removeKeys(Json json, string[] delKeys) {
165   auto result = json;
166   foreach(delKey; delKeys) result.removeKey(delKey);
167   return result;
168 }
169 unittest {
170   auto json = parseJsonString(`{"a":"b", "c":{"d":1}, "e":["f", {"g":"h"}]}`);
171   assert(json.hasValue(Json("b")));
172   assert(json.hasValue(Json("h"), true));
173 }
174 
175 /// Remove key from Json Object
176 Json removeKey(Json json, string delKey) {
177   if (json.type != Json.Type.object) return json;
178 
179   Json result = Json.emptyObject;
180   foreach(kv; json.byKeyValue) if (kv.key != delKey) result[kv.key] = kv.value;
181   return result;
182 }
183 unittest {
184   auto json = parseJsonString(`{"a":"b", "c":{"d":1}, "e":["f", {"g":"h"}]}`);
185   assert(json.hasKey("a"));
186   assert(!json.removeKey("a").hasKey("a"));
187 
188   json = parseJsonString(`{"a":"b", "c":{"d":1}, "e":["f", {"g":"h"}]}`);
189   assert(!json.hasKey("x"));
190   assert(!json.removeKey("x").hasKey("x"));
191   assert(json.removeKey("x").hasKey("a"));
192 }
193 
194 /// Merge jsons objects to one
195 Json mergeJsons(Json[] jsons...) { return mergeJsons(jsons); }
196 /// Merge jsons objects in array to one
197 Json mergeJsons(Json[] jsons) {
198   Json result = Json.emptyObject;
199   foreach(json; jsons) if (json.type == Json.Type.object) {
200     foreach (kv; json.byKeyValue) result[kv.key] = kv.value;
201   }
202   return result;
203 }
204 ///
205 unittest {
206   auto json0 = parseJsonString(`{"a":"b", "c":{"d":1}}`);
207   auto json1 = parseJsonString(`{"e":["f", {"g":"h"}]}`);
208   auto mergeJson = mergeJsons(json0, json1);
209   assert(mergeJson.hasKey("a") && mergeJson.hasKey("e"));
210 }
211 
212 /// Load existing json files in directories
213 Json[] loadJsonsFromDirectories(string[] dirNames) {
214   Json[] results;
215   foreach(dir; dirNames.filter!(a => a.exists)) if (dir.exists) results ~= loadJsonsFromDirectory(dir);
216   return results;
217 }
218 unittest {
219   version(uim_core) {
220     /// TODO
221   }
222 }
223 
224 /// Load existing json file in directories
225 Json[] loadJsonsFromDirectory(string dirName) {
226   // debug writeln("In loadJsonsFromDirectory("~dirName~")");
227   // debug writeln("Found ", fileNames(dirName).length, " files");
228   return loadJsons(fileNames(dirName, true));
229 }
230 unittest {
231   version(uim_core) {
232     /// TODO
233   }
234 }
235 
236 Json[] loadJsons(string[] fileNames) {
237   // debug writeln("Found ", fileNames.length, " names -> ", fileNames);
238   return fileNames.map!(a => loadJson(a)).filter!(a => a != Json(null)).array; 
239 }
240 unittest {
241   version(uim_core) {
242     /// TODO
243   }
244 }
245 
246 Json loadJson(string name) {
247   // debug writeln("In loadJson("~name~")");
248   // debug writeln(name, " exists? ", name.exists);
249   return name.exists ? parseJsonString(readText(name)) : Json(null); 
250 }
251 unittest {
252   version(uim_core) {
253     /// TODO
254   }
255 }
256 
257 Json maxJson(T)(Json[] jsons, string key) {
258   Json result = Json(null);
259   foreach(j; jsons) {
260     if (result == Json(null) && key in j) { result = j; break; }}
261   foreach(j; jsons) {
262     if (key in j && j[key].get!T > result[key].get!T) result = j;  }
263   return result;
264 }
265 unittest {
266   version(uim_core) {
267     /// TODO
268   }
269 }
270 
271 Json minJson(T)(Json[] jsons, string key) {
272   Json result = Json(null);
273   foreach(j; jsons) {
274     if (result == Json(null) && key in j) { results = j; break; }}
275   foreach(j; jsons) {
276     if (key in j && j[key].get!T < result[key].get!T) result = j;  }
277   return result;
278 }
279 unittest {
280   version(uim_core) {
281     /// TODO
282   }
283 }
284 
285 Json toJson(STRINGAA data) {
286   Json json = Json.emptyObject;
287   foreach (k, v; data) json[k] = v;
288   return json;
289 }
290 unittest {
291   version(uim_core) {
292     /// TODO
293   }
294 }
295 
296 Json toJson(string key, string value) {
297   Json json = Json.emptyObject;
298   json[key] = value;
299   return json;
300 }
301 unittest {
302   version(uim_core) {
303     /// TODO
304   }
305 }
306 
307 /// Special case for managing entities
308 Json toJson(UUID id, size_t versionNumber = 0LU) {
309   Json json = Json.emptyObject;
310   json["id"] = id.toString;
311   json["versionNumber"] = versionNumber > 0 ? versionNumber : 1;
312   return json;
313 }
314 unittest {
315   version(uim_core) {
316     /// TODO
317   }
318 }