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 }