1 module painlessjson.painlessjson; 2 3 import std.algorithm : map; 4 import std..string; 5 import std.conv; 6 import std.json; 7 import std.range; 8 import std.traits; 9 import std.typecons : Tuple; 10 import std.typetuple : TypeTuple; 11 import painlessjson.annotations; 12 import painlessjson..string; 13 import painlesstraits; 14 15 version (unittest) 16 { 17 import std.stdio : writeln; 18 import painlessjson.unittesttypes; 19 import dunit.toolkit; 20 } 21 22 struct SerializationOptions 23 { 24 bool alsoAcceptUnderscore; 25 bool convertToUnderscore; 26 } 27 28 enum defaultSerializatonOptions = SerializationOptions(true, false); 29 30 static if (is(typeof(JSONType.integer))) 31 { 32 enum JSONType_int = JSONType.integer; 33 enum JSONType_true = JSONType.true_; 34 enum JSONType_null = JSONType.null_; 35 } 36 else 37 { 38 enum JSONType_int = JSON_TYPE.INTEGER; 39 enum JSONType_true = JSON_TYPE.TRUE; 40 enum JSONType_null = JSON_TYPE.NULL; 41 } 42 43 44 45 //See if we can use something else than __traits(compiles, (T t){JSONValue(t);}) 46 private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (__traits(compiles, (in T t) { 47 JSONValue(t); 48 })) 49 { 50 return JSONValue(object); 51 } 52 53 //See if we can use something else than !__traits(compiles, (T t){JSONValue(t);}) 54 private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (isArray!T && !__traits(compiles, (in T t) { 55 JSONValue(t); 56 })) 57 { 58 JSONValue[] jsonRange; 59 jsonRange = map!((el) => el.toJSON)(object).array; 60 return JSONValue(jsonRange); 61 } 62 63 // AA for simple types, excluding those handled by the JSONValue constructor 64 private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) 65 if (isAssociativeArray!T && isBuiltinType!(KeyType!T) && !isAssociativeArray!(KeyType!T) && 66 !__traits(compiles, (in T t) { JSONValue(t);})) 67 { 68 JSONValue[string] jsonAA; 69 foreach (key, value; object) 70 { 71 72 jsonAA[to!string(key)] = value.toJSON; 73 } 74 return JSONValue(jsonAA); 75 } 76 77 // AA for compound types 78 private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (isAssociativeArray!T && (!isBuiltinType!(KeyType!T) || isAssociativeArray!(KeyType!T))) 79 { 80 JSONValue[string] jsonAA; 81 foreach (key, value; object) 82 { 83 84 jsonAA[key.toJSON.toString] = value.toJSON; 85 } 86 return JSONValue(jsonAA); 87 } 88 89 // platform-independent tuple serializer using _0, _1, ... keys for unnamed keys (because key names could either be _0, _0UL or something else which is implmenetation defined) 90 private JSONValue defaultToJSONImpl(T, SerializationOptions options)(in T object) if (is(T : Tuple!U, U...)) 91 { 92 JSONValue[string] jsonAA; 93 static foreach (i, name; T.fieldNames) 94 { 95 static if (name.length != 0) 96 jsonAA[name] = object[i].toJSON; 97 else 98 jsonAA["_" ~ (cast(int)i).to!string] = object[i].toJSON; 99 } 100 return JSONValue(jsonAA); 101 } 102 103 private JSONValue defaultToJSONImpl(T, SerializationOptions options )(in T object) if (!isBuiltinType!T 104 && !is(T : Tuple!U, U...) && !__traits(compiles, (in T t) { JSONValue(t); })) 105 { 106 JSONValue[string] json; 107 // Getting all member variables (there is probably an easier way) 108 foreach (name; __traits(allMembers, T)) 109 { 110 static if (__traits(compiles, 111 { 112 json[serializationToName!(__traits(getMember, object, name), name, false)] = __traits(getMember, 113 object, name).toJSON; 114 }) && !hasAnyOfTheseAnnotations!(__traits(getMember, object, 115 name), SerializeIgnore, SerializeToIgnore) 116 && isFieldOrProperty!(__traits(getMember, object, name)) 117 && __traits(getProtection, __traits(getMember, object, name)) != "private") 118 { 119 json[serializationToName!(__traits(getMember, object, name), name,(options.convertToUnderscore))] = __traits(getMember, 120 object, name).toJSON; 121 } 122 } 123 return JSONValue(json); 124 } 125 126 /++ 127 Convert any type to JSON<br /> 128 Can be overridden by _toJSON 129 +/ 130 JSONValue defaultToJSON(T, SerializationOptions options = defaultSerializatonOptions)(in T t){ 131 return defaultToJSONImpl!(T, options)(t); 132 } 133 134 /// Template function that converts any object to JSON 135 JSONValue toJSON(T, SerializationOptions options = defaultSerializatonOptions)(in T t) 136 { 137 static if (__traits(compiles, (in T t) { return t._toJSON(); })) 138 { 139 return t._toJSON(); 140 } 141 else 142 return defaultToJSON!(T, options)(t); 143 } 144 145 /// Converting common types 146 unittest 147 { 148 assertEqual(5.toJSON!int, JSONValue(5)); 149 assert(4.toJSON != JSONValue(5)); //TODO: Wait for DUnit to implement assertNotEqual 150 assertEqual(5.4.toJSON, JSONValue(5.4)); 151 assertEqual(toJSON("test"), JSONValue("test")); 152 assertEqual(toJSON(JSONValue("test")), JSONValue("test")); 153 } 154 155 /// Converting InputRanges 156 unittest 157 { 158 assertEqual([1, 2].toJSON.toString, "[1,2]"); 159 } 160 161 /// User structs 162 unittest 163 { 164 Point p; 165 assertEqual(toJSON(p).toString, q{{"x":0,"y":1}}); 166 } 167 168 /// Array of structs 169 unittest 170 { 171 Point[] ps = [Point(-1, 1), Point(2, 3)]; 172 assertEqual(toJSON(ps).toString, q{[{"x":-1,"y":1},{"x":2,"y":3}]}); 173 } 174 175 /// User class 176 unittest 177 { 178 PointC p = new PointC(1, -2); 179 assertEqual(toJSON(p).toString, q{{"x":1,"y":-2}}); 180 } 181 182 /// User class with private fields 183 unittest 184 { 185 PointPrivate p = new PointPrivate(-1, 2); 186 assertEqual(toJSON(p).toString, q{{"x":-1,"y":2}}); 187 auto pnt = p.toJSON.fromJSON!PointPrivate; 188 assertEqual(p.x, -1); 189 assertEqual(p.y, 2); 190 } 191 192 /// User class with defaultToJSON 193 unittest 194 { 195 PointDefaultFromJSON p = new PointDefaultFromJSON(-1, 2); 196 assertEqual(toJSON(p).toString, q{{"_x":-1,"y":2}}); 197 auto pnt = p.toJSON.fromJSON!PointDefaultFromJSON; 198 assertEqual(p.x, -1); 199 assertEqual(p.y, 2); 200 } 201 202 /// User class with private fields and @property 203 unittest 204 { 205 auto p = PointPrivateProperty(-1, 2); 206 assertEqual(toJSON(p).toString, q{{"x":-1,"y":2,"z":1}}); 207 } 208 209 /// User class with SerializedName annotation 210 unittest 211 { 212 auto p = PointSerializationName(-1, 2); 213 assertEqual(toJSON(p)["xOut"].floating, -1); 214 assertEqual(toJSON(p)["yOut"].floating, 2); 215 } 216 217 /// User class with SerializeIgnore annotations 218 unittest 219 { 220 auto p = PointSerializationIgnore(-1, 5, 4); 221 assertEqual(toJSON(p).toString, q{{"z":5}}); 222 } 223 224 /// Array of classes 225 unittest 226 { 227 PointC[] ps = [new PointC(-1, 1), new PointC(2, 3)]; 228 assertEqual(toJSON(ps).toString, q{[{"x":-1,"y":1},{"x":2,"y":3}]}); 229 } 230 231 /// Associative array 232 unittest 233 { 234 string[int] aa = [0 : "a", 1 : "b"]; 235 assert(aa.toJSON.toString == q{{"0":"a","1":"b"}}); 236 Point[int] aaStruct = [0 : Point(-1, 1), 1 : Point(2, 0)]; 237 assertEqual(aaStruct.toJSON.toString, q{{"0":{"x":-1,"y":1},"1":{"x":2,"y":0}}}); 238 assertEqual(["key": "value"].toJSON.toString, q{{"key":"value"}}); 239 } 240 241 /// Associative array containing struct 242 unittest 243 { 244 assertEqual(["test": SimpleStruct("test2")].toJSON().toString, q{{"test":{"str":"test2"}}}); 245 } 246 247 /// Associative array with struct key 248 unittest 249 { 250 assertEqual([SimpleStruct("key"): "value", SimpleStruct("key2"): "value2"].toJSON().toString, q{{"{\"str\":\"key\"}":"value","{\"str\":\"key2\"}":"value2"}}); 251 } 252 253 /// struct with inner struct and AA 254 unittest 255 { 256 auto testStruct = StructWithStructAndAA(["key1": "value1"], ["key2": StructWithStructAndAA.Inner("value2")]); 257 auto converted = testStruct.toJSON(); 258 assertEqual(converted["stringToInner"].toString, q{{"key2":{"str":"value2"}}}); 259 assertEqual(converted["stringToString"].toString, q{{"key1":"value1"}}); 260 } 261 262 /// Unnamed tuples 263 unittest 264 { 265 Tuple!(int, int) point; 266 point[0] = 5; 267 point[1] = 6; 268 assertEqual(toJSON(point).toString, q{{"_0":5,"_1":6}}); 269 } 270 271 /// Named tuples 272 unittest 273 { 274 Tuple!(int, "x", int, "y") point; 275 point.x = 5; 276 point.y = 6; 277 assertEqual(point, fromJSON!(Tuple!(int, "x", int, "y"))(parseJSON(q{{"x":5,"y":6}}))); 278 } 279 280 /// Convert camel case to underscore automatically 281 unittest 282 { 283 CamelCaseConversion value; 284 value.wasCamelCase = 5; 285 value.was_underscore = 7; 286 287 auto valueAsJSON = value.toJSON!(CamelCaseConversion,SerializationOptions(false, true)); 288 289 assertEqual(valueAsJSON["was_camel_case"].integer, 5); 290 assertEqual(valueAsJSON["was_underscore"].integer, 7); 291 } 292 293 /// Overloaded toJSON 294 unittest 295 { 296 class A 297 { 298 double x = 0; 299 double y = 1; 300 JSONValue toJSON() 301 { 302 JSONValue[string] json; 303 json["x"] = x; 304 return JSONValue(json); 305 } 306 307 } 308 309 auto a = new A; 310 assertEqual(a.toJSON.toString, q{{"x":0}}); 311 312 class B 313 { 314 double x = 0; 315 double y = 1; 316 } 317 318 // Both templates will now work for B, so this is ambiguous in D. 319 // Under dmd it looks like the toJSON!T that is loaded first is the one used 320 JSONValue toJSON(T : B)(T b) 321 { 322 JSONValue[string] json; 323 json["x"] = b.x; 324 return JSONValue(json); 325 } 326 327 auto b = new B; 328 assertEqual(b.toJSON.toString, q{{"x":0,"y":1}}); 329 330 class Z 331 { 332 double x = 0; 333 double y = 1; 334 // Adding an extra value 335 JSONValue toJSON() 336 { 337 JSONValue[string] json = painlessjson.toJSON!Z(this).object; 338 json["add"] = "bla".toJSON; 339 return JSONValue(json); 340 } 341 342 } 343 344 auto z = new Z; 345 assertEqual(z.toJSON["x"].floating, 0); 346 assertEqual(z.toJSON["y"].floating, 1); 347 assertEqual(z.toJSON["add"].str, "bla"); 348 } 349 350 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (is(T == JSONValue)) 351 { 352 return json; 353 } 354 355 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isIntegral!T) 356 { 357 return to!T(json.integer); 358 } 359 360 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isFloatingPoint!T) 361 { 362 if (json.type == JSONType_int) 363 return to!T(json.integer); 364 else 365 return to!T(json.floating); 366 } 367 368 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (is(T == string)) 369 { 370 return to!T(json.str); 371 } 372 373 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isBoolean!T) 374 { 375 if (json.type == JSONType_true) 376 return true; 377 else 378 return false; 379 } 380 381 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isArray!T && !is(T == string)) 382 { 383 T t; //Se is we can find another way of finding t.front 384 return map!((js) => fromJSON!(typeof(t.front))(js))(json.array).array; 385 } 386 387 // AA for simple keys 388 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isAssociativeArray!T && isBuiltinType!(KeyType!T) && !isAssociativeArray!(KeyType!T)) 389 { 390 T t; 391 const JSONValue[string] jsonAA = json.object; 392 foreach (k, v; jsonAA) 393 { 394 t[to!(KeyType!T)(k)] = fromJSON!(ValueType!T)(v); 395 } 396 return t; 397 } 398 399 // AA for compound keys 400 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (isAssociativeArray!T && (!isBuiltinType!(KeyType!T) || isAssociativeArray!(KeyType!T))) 401 { 402 KeyType!T keyConverter(string stringKey) { 403 try{ 404 return fromJSON!(KeyType!T)(parseJSON(stringKey)); 405 } 406 catch(JSONException) { 407 throw new Exception("Couldn't convert JSON key \"" ~ stringKey ~ "\" to " ~ KeyType!T.stringof ~ " which is the key type of " ~ T.stringof); 408 } 409 } 410 411 T t; 412 const JSONValue[string] jsonAA = json.object; 413 foreach (k, v; jsonAA) 414 { 415 t[keyConverter(k)] = fromJSON!(ValueType!T)(v); 416 417 } 418 return t; 419 } 420 421 // Default tuple implementation 422 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) if (is(T : Tuple!U, U...)) 423 { 424 T t; 425 const JSONValue[string] jsonAA = json.object; 426 static foreach (i, name; T.fieldNames) 427 {{ 428 static if (name.length != 0) 429 enum effectiveName = name; 430 else 431 enum effectiveName = "_" ~ (cast(int)i).to!string; 432 433 if (auto v = effectiveName in jsonAA) 434 t[i] = fromJSON!(typeof(t[i]))(*v); 435 }} 436 return t; 437 } 438 439 /++ 440 Convert to given type from JSON.<br /> 441 Can be overridden by _fromJSON. 442 +/ 443 private T defaultFromJSONImpl(T, SerializationOptions options)(in JSONValue json) 444 if (!isBuiltinType!T && !is(T == JSONValue) && !is(T : Tuple!U, U...)) 445 { 446 static if (hasAccessibleDefaultsConstructor!(T)) 447 { 448 T t = getIntstanceFromDefaultConstructor!T; 449 mixin("const JSONValue[string] jsonAA = json.object;"); 450 foreach (name; __traits(allMembers, T)) 451 { 452 453 static if (__traits(compiles,{mixin("import " ~ moduleName!(__traits(getMember, t, name)) ~ ";");})) 454 { 455 mixin("import " ~ moduleName!(__traits(getMember, t, name)) ~ ";"); 456 } 457 458 static if (__traits(compiles, 459 mixin( 460 "t." ~ name ~ "= fromJSON!(" ~ fullyQualifiedName!(typeof(__traits(getMember, 461 t, name))) ~ ")(jsonAA[\"aFieldName\"])")) 462 && !hasAnyOfTheseAnnotations!(__traits(getMember, t, name), 463 SerializeIgnore, SerializeFromIgnore) 464 && isFieldOrProperty!(__traits(getMember, t, name))) 465 { 466 enum string fromName = serializationFromName!(__traits(getMember, t, 467 name), name); 468 mixin( 469 "if ( \"" ~ fromName ~ "\" in jsonAA) t." ~ name ~ "= fromJSON!(" ~ fullyQualifiedName!( 470 typeof(__traits(getMember, t, name))) ~ ")(jsonAA[\"" ~ fromName ~ "\"]);"); 471 static if(options.alsoAcceptUnderscore) 472 { 473 mixin( 474 "if ( \"" ~ camelCaseToUnderscore(fromName) ~ "\" in jsonAA) t." ~ name ~ "= fromJSON!(" ~ fullyQualifiedName!( 475 typeof(__traits(getMember, t, name))) ~ ")(jsonAA[\"" ~ camelCaseToUnderscore(fromName) ~ "\"]);"); 476 } 477 } 478 } 479 return t; 480 } 481 else static if (hasAccessibleConstructor!T) 482 { 483 if (__traits(hasMember, T, "__ctor")) 484 { 485 alias Overloads = TypeTuple!(__traits(getOverloads, T, "__ctor")); 486 alias constructorFunctionType = T function(JSONValue value) @system; 487 ulong bestOverloadScore = ulong.max; 488 constructorFunctionType bestOverload; 489 490 // Find the constructor overloads that matches our json content the best 491 foreach (overload; Overloads) 492 { 493 static if (__traits(compiles, 494 { 495 return getInstanceFromCustomConstructor!(T, overload, false)(json); 496 })) 497 { 498 if (jsonValueHasAllFieldsNeeded!(overload, (options.alsoAcceptUnderscore))(json)) 499 { 500 ulong overloadScore = constructorOverloadScore!(overload, (options.alsoAcceptUnderscore))(json); 501 if (overloadScore < bestOverloadScore) 502 { 503 bestOverload = function(JSONValue value) { 504 return getInstanceFromCustomConstructor!(T, overload, (options.alsoAcceptUnderscore))(value); 505 }; 506 bestOverloadScore = overloadScore; 507 } 508 } 509 } 510 } 511 if (bestOverloadScore < ulong.max) 512 { 513 return bestOverload(json); 514 } 515 throw new JSONException( 516 "JSONValue can't satisfy any constructor: " ~ json.toPrettyString); 517 } 518 } 519 else static assert(false, "Cannot be automatically generate a defaultFromJSONImpl for type " ~ T.stringof 520 ~ ". (no accessible and suitable constructors) Create a _fromJSON member method for custom deserialization."); 521 } 522 523 /++ 524 Convert to given type from JSON.<br /> 525 Can be overridden by _fromJSON. 526 +/ 527 T defaultFromJSON(T, SerializationOptions options = defaultSerializatonOptions)(in JSONValue json){ 528 return defaultFromJSONImpl!(T, options)(json); 529 530 } 531 532 template hasAccessibleDefaultsConstructor(T) 533 { 534 static bool helper() 535 { 536 return (is(T == struct) && __traits(compiles, { T t; })) 537 || (is(T == class) && __traits(compiles, { T t = new T; })); 538 } 539 540 enum bool hasAccessibleDefaultsConstructor = helper(); 541 } 542 543 T getIntstanceFromDefaultConstructor(T)() 544 { 545 static if (is(T == struct) && __traits(compiles, { T t; })) 546 { 547 return T(); 548 } 549 else static if (is(T == class) && __traits(compiles, { T t = new T; })) 550 { 551 return new T(); 552 } 553 } 554 555 T getInstanceFromCustomConstructor(T, alias Ctor, bool alsoAcceptUnderscore)(in JSONValue json) 556 { 557 enum params = ParameterIdentifierTuple!(Ctor); 558 alias defaults = ParameterDefaultValueTuple!(Ctor); 559 alias Types = ParameterTypeTuple!(Ctor); 560 Tuple!(Types) args; 561 static foreach (i; 0 .. params.length) 562 {{ 563 enum paramName = params[i]; 564 if (paramName in json.object) 565 { 566 args[i] = fromJSON!(Types[i])(json[paramName]); 567 } else if( alsoAcceptUnderscore && camelCaseToUnderscore(paramName)){ 568 args[i] = fromJSON!(Types[i])(json[camelCaseToUnderscore(paramName)]); 569 } 570 else 571 { 572 // no value specified in json 573 static if (is(defaults[i] == void)) 574 { 575 throw new JSONException( 576 "parameter " ~ paramName ~ " has no default value and was not specified"); 577 } 578 else 579 { 580 args[i] = defaults[i]; 581 } 582 } 583 }} 584 static if (is(T == class)) 585 { 586 return new T(args.expand); 587 } 588 else 589 { 590 return T(args.expand); 591 } 592 } 593 594 bool jsonValueHasAllFieldsNeeded(alias Ctor, bool alsoAcceptUnderscore)(in JSONValue json) 595 { 596 enum params = ParameterIdentifierTuple!(Ctor); 597 alias defaults = ParameterDefaultValueTuple!(Ctor); 598 alias Types = ParameterTypeTuple!(Ctor); 599 Tuple!(Types) args; 600 static foreach (i; 0 .. params.length) 601 {{ 602 enum paramName = params[i]; 603 if (!((paramName in json.object) || ( alsoAcceptUnderscore && (camelCaseToUnderscore(paramName) in json.object))) && is(defaults[i] == void)) 604 { 605 return false; 606 } 607 }} 608 return true; 609 } 610 611 ulong constructorOverloadScore(alias Ctor, bool alsoAcceptUnderscore)(in JSONValue json) 612 { 613 enum params = ParameterIdentifierTuple!(Ctor); 614 alias defaults = ParameterDefaultValueTuple!(Ctor); 615 alias Types = ParameterTypeTuple!(Ctor); 616 Tuple!(Types) args; 617 ulong overloadScore = json.object.length; 618 static foreach (i; 0 .. params.length) 619 {{ 620 enum paramName = params[i]; 621 if (paramName in json.object || ( alsoAcceptUnderscore && (camelCaseToUnderscore(paramName) in json.object))) 622 { 623 overloadScore--; 624 } 625 }} 626 return overloadScore; 627 } 628 629 template hasAccessibleConstructor(T) 630 { 631 static bool helper() 632 { 633 if (__traits(hasMember, T, "__ctor")) 634 { 635 alias Overloads = TypeTuple!(__traits(getOverloads, T, "__ctor")); 636 bool anyInstanciates = false; 637 foreach (overload; Overloads) 638 { 639 anyInstanciates |= __traits(compiles, getInstanceFromCustomConstructor!(T, 640 overload, false)(JSONValue())); 641 } 642 return anyInstanciates; 643 } 644 } 645 646 enum bool hasAccessibleConstructor = helper(); 647 } 648 649 /// Convert from JSONValue to any other type 650 T fromJSON(T, SerializationOptions options = defaultSerializatonOptions)(in JSONValue json) 651 { 652 static if (__traits(compiles, { return T._fromJSON(json); })) 653 { 654 return T._fromJSON(json); 655 } 656 else 657 { 658 if (json.type == JSONType_null) 659 return T.init; 660 return defaultFromJSON!(T,options)(json); 661 } 662 } 663 664 unittest { 665 struct P 666 { string name; } 667 668 auto jv = parseJSON(`{"name": null}`); 669 auto j = fromJSON!P(jv); 670 } 671 672 /// Converting common types 673 unittest 674 { 675 assertEqual(fromJSON!int(JSONValue(1)), 1); 676 assertEqual(fromJSON!double(JSONValue(1.0)), 1); 677 assertEqual(fromJSON!double(JSONValue(1.3)), 1.3); 678 assertEqual(fromJSON!string(JSONValue("str")), "str"); 679 assertEqual(fromJSON!bool(JSONValue(true)), true); 680 assertEqual(fromJSON!bool(JSONValue(false)), false); 681 assertEqual(fromJSON!JSONValue(JSONValue(true)), JSONValue(true)); 682 } 683 684 /// Converting arrays 685 unittest 686 { 687 assertEqual(fromJSON!(int[])(toJSON([1, 2])), [1, 2]); 688 assertEqual(fromJSON!(Point[])(parseJSON(q{[{"x":-1,"y":2},{"x":3,"y":4}]})), [Point(-1,2),Point(3,4)]); 689 } 690 691 /// Array as member of other class 692 unittest 693 { 694 // Types need to be defined in different module, otherwise 695 // type is not known at compile time 696 import painlessjson.unittesttypes_local_import; 697 698 string jsonString = q{[ {"duration": "10"} ]}; 699 Route[] routes = parseJSON(jsonString).fromJSON!(Route[]); 700 assertEqual(routes.length, 1); 701 702 jsonString = q{{"routes":[ {"duration": "10"} ] }}; 703 JourneyPlan jp; 704 jp = parseJSON(jsonString).fromJSON!JourneyPlan; 705 assertEqual(jp.routes.length, 1); 706 } 707 708 /// Associative arrays 709 unittest 710 { 711 string[int] aaInt = [0 : "a", 1 : "b"]; 712 assertEqual(aaInt, fromJSON!(string[int])(parseJSON(q{{"0" : "a", "1": "b"}}))); 713 714 string[string] aaString = ["hello" : "world", "json" : "painless"]; 715 assertEqual(aaString, fromJSON!(string[string])(parseJSON(q{{"hello" : "world", "json" : "painless"}}))); 716 } 717 718 /// Associative array containing struct 719 unittest 720 { 721 auto parsed = fromJSON!(SimpleStruct[string])(parseJSON(q{{"key": {"str": "value"}}})); 722 assertEqual(parsed , ["key": SimpleStruct("value")]); 723 } 724 725 /// Associative array with struct key 726 unittest 727 { 728 JSONValue value = parseJSON(q{{"{\"str\":\"key\"}":"value", "{\"str\":\"key2\"}":"value2"}}); 729 auto parsed = fromJSON!(string[SimpleStruct])(value); 730 assertEqual( 731 parsed 732 , [SimpleStruct("key"): "value", SimpleStruct("key2"): "value2"]); 733 } 734 735 /// struct with inner struct and AA 736 unittest 737 { 738 auto testStruct = StructWithStructAndAA(["key1": "value1"], ["key2": StructWithStructAndAA.Inner("value2")]); 739 auto testJSON = parseJSON(q{{"stringToInner":{"key2":{"str":"value2"}},"stringToString":{"key1":"value1"}}}); 740 assertEqual(fromJSON!StructWithStructAndAA(testJSON), testStruct); 741 } 742 743 /// Error reporting from inner objects 744 unittest 745 { 746 import std.exception : collectExceptionMsg; 747 import std.algorithm : canFind; 748 void throwFunc() { 749 fromJSON!(string[SimpleStruct])(parseJSON(q{{"{\"str\": \"key1\"}": "value", "key2":"value2"}})); 750 } 751 auto errorMessage = collectExceptionMsg(throwFunc()); 752 assert(errorMessage.canFind("key2")); 753 assert(errorMessage.canFind("string[SimpleStruct]")); 754 assert(!errorMessage.canFind("key1")); 755 } 756 757 /// Structs from JSON 758 unittest 759 { 760 auto p = fromJSON!Point(parseJSON(q{{"x":-1,"y":2}})); 761 assertEqual(p.x, -1); 762 assertEqual(p.y, 2); 763 p = fromJSON!Point(parseJSON(q{{"x":2}})); 764 assertEqual(p.x, 2); 765 assertEqual(p.y, 1); 766 p = fromJSON!Point(parseJSON(q{{"y":3}})); 767 assertEqual(p.x, 0); 768 assertEqual(p.y, 3); 769 p = fromJSON!Point(parseJSON(q{{"x":-1,"y":2,"z":3}})); 770 assertEqual(p.x, -1); 771 assertEqual(p.y, 2); 772 } 773 774 /// Class from JSON 775 unittest 776 { 777 auto p = fromJSON!PointC(parseJSON(q{{"x":-1,"y":2}})); 778 assertEqual(p.x, -1); 779 assertEqual(p.y, 2); 780 } 781 782 /** 783 Convert class from JSON using "_fromJSON" 784 */ 785 786 unittest 787 { 788 auto p = fromJSON!PointPrivate(parseJSON(q{{"x":-1,"y":2}})); 789 assertEqual(p.x, -1); 790 assertEqual(p.y, 2); 791 } 792 793 /// Convert struct from JSON using properties 794 795 unittest 796 { 797 auto p = fromJSON!PointPrivateProperty(parseJSON(q{{"x":-1,"y":2,"z":3}})); 798 assertEqual(p.x, -1); 799 assertEqual(p.y, 2); 800 } 801 802 /// User class with SerializedName annotation 803 unittest 804 { 805 auto p = fromJSON!PointSerializationName(parseJSON(q{{"xOut":-1,"yOut":2}})); 806 assertEqual(p.x, 2); 807 assertEqual(p.y, -1); 808 } 809 810 /// User class with SerializeIgnore annotations 811 unittest 812 { 813 auto p = fromJSON!PointSerializationIgnore(parseJSON(q{{"z":15}})); 814 assertEqual(p.x, 0); 815 assertEqual(p.y, 1); 816 assertEqual(p.z, 15); 817 } 818 819 /// Unnamed tuples 820 unittest 821 { 822 Tuple!(int, int) point; 823 point[0] = 5; 824 point[1] = 6; 825 assertEqual(point, fromJSON!(Tuple!(int, int))(parseJSON(q{{"_0":5,"_1":6}}))); 826 } 827 828 /// No default constructor 829 unittest 830 { 831 auto p = fromJSON!PointUseConstructor(parseJSON(q{{"x":2, "y":5}})); 832 assertEqual(p.x, 2); 833 assertEqual(p.y, 5); 834 } 835 836 /// Multiple constructors and all JSON-values are there 837 unittest 838 { 839 auto person = fromJSON!IdAndName(parseJSON(q{{"id":34, "name": "Jason Pain"}})); 840 assertEqual(person.id, 34); 841 assertEqual(person.name, "Jason Pain"); 842 } 843 844 /// Multiple constructors and some JSON-values are missing 845 unittest 846 { 847 auto person = fromJSON!IdAndName(parseJSON(q{{"id":34}})); 848 assertEqual(person.id, 34); 849 assertEqual(person.name, "Undefined"); 850 } 851 852 /// Accept underscore and convert it to camelCase automatically 853 unittest 854 { 855 auto value = fromJSON!CamelCaseConversion(parseJSON(q{{"was_camel_case":8,"was_underscore":9}})); 856 assertEqual(value.wasCamelCase, 8); 857 assertEqual(value.was_underscore, 9); 858 }