1 /* 2 *Copyright (C) 2018 Laurent Tréguier 3 * 4 *This file is part of DLS. 5 * 6 *DLS is free software: you can redistribute it and/or modify 7 *it under the terms of the GNU General Public License as published by 8 *the Free Software Foundation, either version 3 of the License, or 9 *(at your option) any later version. 10 * 11 *DLS is distributed in the hope that it will be useful, 12 *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 *GNU General Public License for more details. 15 * 16 *You should have received a copy of the GNU General Public License 17 *along with DLS. If not, see <http://www.gnu.org/licenses/>. 18 * 19 */ 20 21 module dls.util.json; 22 23 import std.json : JSONValue; 24 import std.traits : isArray, isAssociativeArray, isBoolean, isNumeric, isSomeChar, isSomeString; 25 import dls.util.nullable : Nullable; 26 27 /++ 28 Converts a `JSONValue` to an object of type `T` by filling its fields with the JSON's fields. 29 +/ 30 T convertFromJSON(T)(JSONValue json) 31 if ((is(T == class) || is(T == struct)) && !is(T == JSONValue)) 32 { 33 import std.json : JSONException, JSONType; 34 import std.meta : Alias; 35 import std.traits : isSomeFunction, isType; 36 37 static if (is(T == class)) 38 { 39 auto result = new T(); 40 } 41 else 42 { 43 auto result = T(); 44 } 45 46 if (json.type != JSONType.object) 47 { 48 throw new JSONException(json.toString() ~ " is not an object type"); 49 } 50 51 foreach (member; __traits(allMembers, T)) 52 { 53 alias m = Alias!(__traits(getMember, T, member)); 54 55 static if (__traits(getProtection, m) == "public" && !isType!(m) && !isSomeFunction!(m)) 56 { 57 try 58 { 59 __traits(getMember, result, member) = convertFromJSON!(typeof(__traits(getMember, 60 result, member)))(json[normalizeMemberName(member)]); 61 } 62 catch (JSONException e) 63 { 64 } 65 } 66 } 67 68 return result; 69 } 70 71 T convertFromJSON(T)(JSONValue json) if (is(T == interface)) 72 { 73 static assert(false, "Cannot instantiate an interface"); 74 } 75 76 version (unittest) 77 { 78 struct TestStruct 79 { 80 uint uinteger; 81 JSONValue json; 82 } 83 84 class TestClass 85 { 86 int integer; 87 float floating; 88 string text; 89 int[] array; 90 string[string] dictionary; 91 TestStruct testStruct; 92 } 93 } 94 95 unittest 96 { 97 import std.json : parseJSON; 98 99 immutable jsonString = `{ 100 "integer": 42, 101 "floating": 3.0, 102 "text": "Hello world", 103 "array": [0, 1, 2], 104 "dictionary": { 105 "key1": "value1", 106 "key2": "value2", 107 "key3": "value3" 108 }, 109 "testStruct": { 110 "uinteger": 16, 111 "json": { 112 "key": "value" 113 } 114 } 115 }`; 116 117 immutable testClass = convertFromJSON!TestClass(parseJSON(jsonString)); 118 assert(testClass.integer == 42); 119 assert(testClass.floating == 3.0); 120 assert(testClass.text == "Hello world"); 121 assert(testClass.array == [0, 1, 2]); 122 immutable dictionary = ["key1" : "value1", "key2" : "value2", "key3" : "value3"]; 123 assert(testClass.dictionary == dictionary); 124 assert(testClass.testStruct.uinteger == 16); 125 assert(testClass.testStruct.json["key"].str == "value"); 126 } 127 128 N convertFromJSON(N : Nullable!T, T)(JSONValue json) 129 { 130 import std.json : JSONType; 131 import dls.util.nullable : nullable; 132 133 return (json.type == JSONType.null_) ? N() : convertFromJSON!T(json).nullable; 134 } 135 136 unittest 137 { 138 auto json = JSONValue(42); 139 auto result = convertFromJSON!(Nullable!int)(json); 140 assert(!result.isNull && result.get() == json.integer); 141 142 json = JSONValue(null); 143 assert(convertFromJSON!(Nullable!int)(json).isNull); 144 } 145 146 T convertFromJSON(T : JSONValue)(JSONValue json) 147 { 148 import dls.util.nullable : nullable; 149 150 return json.nullable; 151 } 152 153 unittest 154 { 155 assert(convertFromJSON!JSONValue(JSONValue(42)) == JSONValue(42)); 156 } 157 158 T convertFromJSON(T)(JSONValue json) if (isNumeric!T || isSomeChar!T) 159 { 160 import std.conv : to; 161 import std.json : JSONException, JSONType; 162 163 switch (json.type) 164 { 165 case JSONType.null_, JSONType.false_: 166 return 0.to!T; 167 168 case JSONType.true_: 169 return 1.to!T; 170 171 case JSONType.float_: 172 return json.floating.to!T; 173 174 case JSONType.integer: 175 return json.integer.to!T; 176 177 case JSONType.uinteger: 178 return json.uinteger.to!T; 179 180 case JSONType..string: 181 return json.str.to!T; 182 183 default: 184 throw new JSONException(json.toString() ~ " is not a numeric type"); 185 } 186 } 187 188 unittest 189 { 190 assert(convertFromJSON!float(JSONValue(3.0)) == 3.0); 191 assert(convertFromJSON!int(JSONValue(42)) == 42); 192 assert(convertFromJSON!uint(JSONValue(42U)) == 42U); 193 assert(convertFromJSON!char(JSONValue('a')) == 'a'); 194 195 // quirky JSON cases 196 197 assert(convertFromJSON!int(JSONValue(null)) == 0); 198 assert(convertFromJSON!int(JSONValue(false)) == 0); 199 assert(convertFromJSON!int(JSONValue(true)) == 1); 200 assert(convertFromJSON!int(JSONValue("42")) == 42); 201 assert(convertFromJSON!char(JSONValue("a")) == 'a'); 202 } 203 204 T convertFromJSON(T)(JSONValue json) if (isBoolean!T) 205 { 206 import std.json : JSONType; 207 208 switch (json.type) 209 { 210 case JSONType.null_, JSONType.false_: 211 return false; 212 213 case JSONType.float_: 214 return json.floating != 0; 215 216 case JSONType.integer: 217 return json.integer != 0; 218 219 case JSONType.uinteger: 220 return json.uinteger != 0; 221 222 case JSONType..string: 223 return json.str.length > 0; 224 225 default: 226 return true; 227 } 228 } 229 230 unittest 231 { 232 assert(convertFromJSON!bool(JSONValue(false)) == false); 233 assert(convertFromJSON!bool(JSONValue(true)) == true); 234 235 // quirky JSON cases 236 237 assert(convertFromJSON!bool(JSONValue(null)) == false); 238 assert(convertFromJSON!bool(JSONValue(0.0)) == false); 239 assert(convertFromJSON!bool(JSONValue(0)) == false); 240 assert(convertFromJSON!bool(JSONValue(0U)) == false); 241 assert(convertFromJSON!bool(JSONValue("")) == false); 242 243 assert(convertFromJSON!bool(JSONValue(3.0)) == true); 244 assert(convertFromJSON!bool(JSONValue(42)) == true); 245 assert(convertFromJSON!bool(JSONValue(42U)) == true); 246 assert(convertFromJSON!bool(JSONValue("Hello world")) == true); 247 assert(convertFromJSON!bool(JSONValue(new int[0])) == true); 248 } 249 250 T convertFromJSON(T)(JSONValue json) 251 if (isSomeString!T || is(T : string) || is(T : wstring) || is(T : dstring)) 252 { 253 import std.conv : to; 254 import std.json : JSONException, JSONType; 255 256 static if (is(T == enum)) 257 { 258 foreach (member; __traits(allMembers, T)) 259 { 260 auto m = __traits(getMember, T, member); 261 262 if (json.str == m) 263 { 264 return m; 265 } 266 } 267 268 throw new JSONException(json.toString() ~ " is not a member of " ~ typeid(T).toString()); 269 } 270 else 271 { 272 return (json.type == JSONType..string ? json.str : json.toString()).to!T; 273 } 274 } 275 276 unittest 277 { 278 enum Operation : string 279 { 280 create = "create", 281 delete_ = "delete" 282 } 283 284 assert(convertFromJSON!Operation(JSONValue("create")) == Operation.create); 285 assert(convertFromJSON!Operation(JSONValue("delete")) == Operation.delete_); 286 287 auto json = JSONValue("Hello"); 288 assert(convertFromJSON!string(json) == json.str); 289 assert(convertFromJSON!(char[])(json) == json.str); 290 assert(convertFromJSON!wstring(json) == "Hello"w); 291 assert(convertFromJSON!(wchar[])(json) == "Hello"w); 292 assert(convertFromJSON!dstring(json) == "Hello"d); 293 assert(convertFromJSON!(dchar[])(json) == "Hello"d); 294 295 // beware of the fact that JSONValue treats chars as integers; this returns "97" and not "a" 296 assert(convertFromJSON!string(JSONValue('a')) != "a"); 297 assert(convertFromJSON!string(JSONValue("a")) == "a"); 298 299 enum TestEnum : string 300 { 301 hello = "hello", 302 world = "world" 303 } 304 305 assert(convertFromJSON!TestEnum(JSONValue("hello")) == TestEnum.hello); 306 assert(convertFromJSON!TestEnum(JSONValue("world")) == TestEnum.world); 307 308 // quirky JSON cases 309 310 assert(convertFromJSON!string(JSONValue(null)) == "null"); 311 assert(convertFromJSON!string(JSONValue(false)) == "false"); 312 assert(convertFromJSON!string(JSONValue(true)) == "true"); 313 } 314 315 T convertFromJSON(T : U[], U)(JSONValue json) 316 if (isArray!T && !isSomeString!T && !is(T : string) && !is(T : wstring) && !is(T : dstring)) 317 { 318 import std.algorithm : map; 319 import std.array : array; 320 import std.json : JSONException, JSONType; 321 322 switch (json.type) 323 { 324 case JSONType.null_: 325 return []; 326 327 case JSONType.false_: 328 return [convertFromJSON!U(JSONValue(false))]; 329 330 case JSONType.true_: 331 return [convertFromJSON!U(JSONValue(true))]; 332 333 case JSONType.array: 334 return json.array.map!(value => convertFromJSON!U(value)).array; 335 336 case JSONType.object: 337 throw new JSONException(json.toString() ~ " is not a string type"); 338 339 default: 340 return [convertFromJSON!U(json)]; 341 } 342 } 343 344 unittest 345 { 346 assert(convertFromJSON!(int[])(JSONValue([0, 1, 2, 3])) == [0, 1, 2, 3]); 347 348 // quirky JSON cases 349 350 assert(convertFromJSON!(int[])(JSONValue(null)) == []); 351 assert(convertFromJSON!(int[])(JSONValue(false)) == [0]); 352 assert(convertFromJSON!(bool[])(JSONValue(true)) == [true]); 353 assert(convertFromJSON!(float[])(JSONValue(3.0)) == [3.0]); 354 assert(convertFromJSON!(int[])(JSONValue(42)) == [42]); 355 assert(convertFromJSON!(uint[])(JSONValue(42U)) == [42U]); 356 assert(convertFromJSON!(string[])(JSONValue("Hello")) == ["Hello"]); 357 } 358 359 T convertFromJSON(T : U[string], U)(JSONValue json) if (isAssociativeArray!T) 360 { 361 import std.conv : text; 362 import std.json : JSONException, JSONType; 363 364 U[string] result; 365 366 switch (json.type) 367 { 368 case JSONType.null_: 369 return result; 370 371 case JSONType.object: 372 foreach (key, value; json.object) 373 { 374 result[key] = convertFromJSON!U(value); 375 } 376 377 break; 378 379 case JSONType.array: 380 foreach (key, value; json.array) 381 { 382 result[text(key)] = convertFromJSON!U(value); 383 } 384 385 break; 386 387 default: 388 throw new JSONException(json.toString() ~ " is not an object type"); 389 } 390 391 return result; 392 } 393 394 unittest 395 { 396 auto dictionary = ["hello" : 42, "world" : 0]; 397 assert(convertFromJSON!(int[string])(JSONValue(dictionary)) == dictionary); 398 399 // quirky JSON cases 400 401 assert(convertFromJSON!(int[string])(JSONValue([16, 42])) == ["0" : 16, "1" : 42]); 402 dictionary.clear(); 403 assert(convertFromJSON!(int[string])(JSONValue(null)) == dictionary); 404 } 405 406 Nullable!JSONValue convertToJSON(T)(T value) 407 if ((is(T == class) || is(T == struct) || is(T == interface)) && !is(T == JSONValue)) 408 { 409 import std.meta : Alias; 410 import std.traits : isSomeFunction, isType; 411 import dls.util.nullable : nullable; 412 413 static if (is(T == class)) 414 { 415 if (value is null) 416 { 417 return JSONValue(null).nullable; 418 } 419 } 420 421 auto result = JSONValue(); 422 423 foreach (member; __traits(allMembers, T)) 424 { 425 alias m = Alias!(__traits(getMember, T, member)); 426 427 static if (__traits(getProtection, m) == "public" && !isType!(m) && !isSomeFunction!(m)) 428 { 429 auto json = convertToJSON!(typeof(__traits(getMember, value, member)))( 430 __traits(getMember, value, member)); 431 432 if (!json.isNull) 433 { 434 result[normalizeMemberName(member)] = json.get(); 435 } 436 } 437 } 438 439 return result.nullable; 440 } 441 442 unittest 443 { 444 import std.json : parseJSON; 445 446 auto testClass = new TestClass(); 447 testClass.integer = 42; 448 testClass.floating = 3.5; 449 testClass.text = "Hello world"; 450 testClass.array = [0, 1, 2]; 451 testClass.dictionary = ["key1" : "value1", "key2" : "value2"]; 452 testClass.testStruct = TestStruct(); 453 testClass.testStruct.uinteger = 16; 454 testClass.testStruct.json = JSONValue(["key1" : "value1", "key2" : "value2"]); 455 456 auto jsonString = `{ 457 "integer": 42, 458 "floating": 3.5, 459 "text": "Hello world", 460 "array": [0, 1, 2], 461 "dictionary": { 462 "key1": "value1", 463 "key2": "value2" 464 }, 465 "testStruct": { 466 "uinteger": 16, 467 "json": { 468 "key1": "value1", 469 "key2": "value2" 470 } 471 } 472 }`; 473 474 auto json = convertToJSON(testClass); 475 // parseJSON() will parse `uinteger` as a regular integer, meaning that the JSON's aren't considered equal, 476 // even though technically they are equivalent (16 as int or as uint is technically the same value), which 477 // is why .toString() is used here 478 assert(json.get().toString() == parseJSON(jsonString).toString()); 479 480 TestClass nullTestClass = null; 481 auto nullJson = convertToJSON(nullTestClass); 482 assert(!nullJson.isNull && nullJson.get().isNull); 483 } 484 485 Nullable!JSONValue convertToJSON(N : Nullable!T, T)(N value) 486 { 487 return value.isNull ? Nullable!JSONValue() : convertToJSON!T(value.get()); 488 } 489 490 unittest 491 { 492 assert(convertToJSON(Nullable!int()) == Nullable!JSONValue()); 493 assert(convertToJSON(Nullable!int(42)) == JSONValue(42)); 494 } 495 496 Nullable!JSONValue convertToJSON(T)(T value) 497 if ((!is(T == class) && !is(T == struct) && !is(T == interface)) || is(T == JSONValue)) 498 { 499 import dls.util.nullable : nullable; 500 501 return JSONValue(value).nullable; 502 } 503 504 unittest 505 { 506 assert(convertToJSON(3.0) == JSONValue(3.0)); 507 assert(convertToJSON(42) == JSONValue(42)); 508 assert(convertToJSON(42U) == JSONValue(42U)); 509 assert(convertToJSON(false) == JSONValue(false)); 510 assert(convertToJSON(true) == JSONValue(true)); 511 assert(convertToJSON('a') == JSONValue('a')); 512 assert(convertToJSON("Hello world") == JSONValue("Hello world")); 513 assert(convertToJSON(JSONValue(42)) == JSONValue(42)); 514 } 515 516 Nullable!JSONValue convertToJSON(T : U[], U)(T value) 517 if (isArray!T && !isSomeString!T && !is(T : string) && !is(T : wstring) && !is(T : dstring)) 518 { 519 import std.algorithm : map; 520 import std.array : array; 521 import dls.util.nullable : nullable; 522 523 return JSONValue(value.map!(item => convertToJSON(item))() 524 .map!(json => json.isNull ? JSONValue(null) : json).array).nullable; 525 } 526 527 unittest 528 { 529 assert(convertToJSON([0, 1, 2]) == JSONValue([0, 1, 2])); 530 assert(convertToJSON(["hello", "world"]) == JSONValue(["hello", "world"])); 531 } 532 533 Nullable!JSONValue convertToJSON(T : U[K], U, K)(T value) if (isAssociativeArray!T) 534 { 535 import std.conv : text; 536 import dls.util.nullable : nullable; 537 538 auto result = JSONValue(); 539 540 foreach (key; value.keys) 541 { 542 auto json = convertToJSON(value[key]); 543 result[text(key)] = json.isNull ? JSONValue(null) : json; 544 } 545 546 return result.nullable; 547 } 548 549 unittest 550 { 551 assert(convertToJSON(["hello" : 16, "world" : 42]) == JSONValue(["hello" : 16, "world" : 42])); 552 assert(convertToJSON(['a' : 16, 'b' : 42]) == JSONValue(["a" : 16, "b" : 42])); 553 assert(convertToJSON([0 : 16, 1 : 42]) == JSONValue(["0" : 16, "1" : 42])); 554 } 555 556 /++ 557 Removes underscores from names. Some protocol variable names can be reserved 558 names (like `version`) and thus have an added underscore in their protocol 559 definition. 560 +/ 561 private string normalizeMemberName(const string name) 562 { 563 import std..string : endsWith; 564 565 return name.endsWith('_') ? name[0 .. $ - 1] : name; 566 } 567 568 unittest 569 { 570 assert(normalizeMemberName("hello") == "hello"); 571 assert(normalizeMemberName("hello_") == "hello"); 572 assert(normalizeMemberName("_hello") == "_hello"); 573 assert(normalizeMemberName("hel_lo") == "hel_lo"); 574 }