1 /** 2 * Import all public modules for jsonizer. 3 * 4 * Authors: <a href="https://github.com/rcorre">rcorre</a> 5 * License: <a href="http://opensource.org/licenses/MIT">MIT</a> 6 * Copyright: Copyright © 2015, rcorre 7 * Date: 3/23/15 8 */ 9 module jsonizer; 10 11 public import jsonizer.tojson; 12 public import jsonizer.fromjson; 13 public import jsonizer.jsonize; 14 public import jsonizer.common; 15 public import jsonizer.exceptions; 16 17 /// object serialization -- fields only 18 unittest { 19 import std.math : approxEqual; 20 21 static class Fields { 22 // classes must have a no-args constructor 23 // or a constructor marked with @jsonize (see later example) 24 this() { } 25 26 this(int iVal, float fVal, string sVal, int[] aVal, string noJson) { 27 i = iVal; 28 f = fVal; 29 s = sVal; 30 a = aVal; 31 dontJsonMe = noJson; 32 } 33 34 mixin JsonizeMe; 35 36 @jsonize { // fields to jsonize -- test different access levels 37 public int i; 38 protected float f; 39 public int[] a; 40 private string s; 41 } 42 string dontJsonMe; 43 44 override bool opEquals(Object o) { 45 auto other = cast(Fields) o; 46 return i == other.i && s == other.s && a == other.a && f.approxEqual(other.f); 47 } 48 } 49 50 auto obj = new Fields(1, 4.2, "tally ho!", [9, 8, 7, 6], "blarg"); 51 auto json = toJSON!Fields(obj); 52 53 assert(json.object["i"].integer == 1); 54 assert(json.object["f"].floating.approxEqual(4.2)); 55 assert(json.object["s"].str == "tally ho!"); 56 assert(json.object["a"].array[0].integer == 9); 57 assert("dontJsonMe" !in json.object); 58 59 // reconstruct from json 60 auto r = fromJSON!Fields(json); 61 assert(r.i == 1); 62 assert(r.f.approxEqual(4.2)); 63 assert(r.s == "tally ho!"); 64 assert(r.a == [9, 8, 7, 6]); 65 assert(r.dontJsonMe is null); 66 67 // array of objects 68 auto a = [ 69 new Fields(1, 4.2, "tally ho!", [9, 8, 7, 6], "blarg"), 70 new Fields(7, 42.2, "yea merrily", [1, 4, 6, 4], "asparagus") 71 ]; 72 73 // serialize array of user objects to json 74 auto jsonArray = toJSON!(Fields[])(a); 75 // reconstruct from json 76 assert(fromJSON!(Fields[])(jsonArray) == a); 77 } 78 79 /// object serialization with properties 80 unittest { 81 import std.math : approxEqual; 82 83 static class Props { 84 this() { } // class must have a no-args ctor 85 86 this(int iVal, float fVal, string sVal, string noJson) { 87 _i = iVal; 88 _f = fVal; 89 _s = sVal; 90 _dontJsonMe = noJson; 91 } 92 93 mixin JsonizeMe; 94 95 @property { 96 // jsonize ref property accessing private field 97 @jsonize ref int i() { return _i; } 98 // jsonize property with non-trivial get/set methods 99 @jsonize float f() { return _f - 3; } // the jsonized value will equal _f - 3 100 float f(float val) { return _f = val + 5; } // 5 will be added to _f when retrieving from json 101 // don't jsonize these properties 102 ref string s() { return _s; } 103 ref string dontJsonMe() { return _dontJsonMe; } 104 } 105 106 private: 107 int _i; 108 float _f; 109 @jsonize string _s; 110 string _dontJsonMe; 111 } 112 113 auto obj = new Props(1, 4.2, "tally ho!", "blarg"); 114 auto json = toJSON(obj); 115 116 assert(json.object["i"].integer == 1); 117 assert(json.object["f"].floating.approxEqual(4.2 - 3.0)); // property should have subtracted 3 on retrieval 118 assert(json.object["_s"].str == "tally ho!"); 119 assert("dontJsonMe" !in json.object); 120 121 auto r = fromJSON!Props(json); 122 assert(r.i == 1); 123 assert(r._f.approxEqual(4.2 - 3.0 + 5.0)); // property accessor should add 5 124 assert(r._s == "tally ho!"); 125 assert(r.dontJsonMe is null); 126 } 127 128 /// object serialization with custom constructor 129 unittest { 130 import std.conv : to; 131 import std.json : parseJSON; 132 import std.math : approxEqual; 133 import jsonizer.tojson : toJSON; 134 135 static class Custom { 136 mixin JsonizeMe; 137 138 this(int i) { 139 _i = i; 140 _s = "something"; 141 _f = 10.2; 142 } 143 144 @jsonize this(int _i, string _s, float _f = 20.2) { 145 this._i = _i; 146 this._s = _s ~ " jsonized"; 147 this._f = _f; 148 } 149 150 @jsonize this(double d) { // alternate ctor 151 _f = d.to!float; 152 _s = d.to!string; 153 _i = d.to!int; 154 } 155 156 private: 157 @jsonize { 158 string _s; 159 float _f; 160 int _i; 161 } 162 } 163 164 auto c = new Custom(12); 165 auto json = toJSON(c); 166 assert(json.object["_i"].integer == 12); 167 assert(json.object["_s"].str == "something"); 168 assert(json.object["_f"].floating.approxEqual(10.2)); 169 auto c2 = fromJSON!Custom(json); 170 assert(c2._i == 12); 171 assert(c2._s == "something jsonized"); 172 assert(c2._f.approxEqual(10.2)); 173 174 // test alternate ctor 175 json = parseJSON(`{"d" : 5}`); 176 c = json.fromJSON!Custom; 177 assert(c._f.approxEqual(5) && c._i == 5 && c._s == "5"); 178 } 179 180 /// struct serialization 181 unittest { 182 import std.math : approxEqual; 183 184 static struct S { 185 mixin JsonizeMe; 186 187 @jsonize { 188 int x; 189 float f; 190 string s; 191 } 192 int dontJsonMe; 193 194 this(int x, float f, string s, int noJson) { 195 this.x = x; 196 this.f = f; 197 this.s = s; 198 this.dontJsonMe = noJson; 199 } 200 } 201 202 auto s = S(5, 4.2, "bogus", 7); 203 auto json = toJSON(s); // serialize a struct 204 205 assert(json.object["x"].integer == 5); 206 assert(json.object["f"].floating.approxEqual(4.2)); 207 assert(json.object["s"].str == "bogus"); 208 assert("dontJsonMe" !in json.object); 209 210 auto r = fromJSON!S(json); 211 assert(r.x == 5); 212 assert(r.f.approxEqual(4.2)); 213 assert(r.s == "bogus"); 214 assert(r.dontJsonMe == int.init); 215 } 216 217 /// json file I/O 218 unittest { 219 import std.file : remove; 220 import jsonizer.fromjson : readJSON; 221 import jsonizer.tojson : writeJSON; 222 223 enum file = "test.json"; 224 scope(exit) remove(file); 225 226 static struct Data { 227 mixin JsonizeMe; 228 229 @jsonize { 230 int x; 231 string s; 232 float f; 233 } 234 } 235 236 // write an array of user-defined structs 237 auto array = [Data(5, "preposterous", 12.7), Data(8, "tesseract", -2.7), Data(5, "baby sloths", 102.7)]; 238 file.writeJSON(array); 239 auto readBack = file.readJSON!(Data[]); 240 assert(readBack == array); 241 242 // now try an associative array 243 auto aa = ["alpha": Data(27, "yams", 0), "gamma": Data(88, "spork", -99.999)]; 244 file.writeJSON(aa); 245 auto aaReadBack = file.readJSON!(Data[string]); 246 assert(aaReadBack == aa); 247 } 248 249 /// inheritance 250 unittest { 251 import std.math : approxEqual; 252 static class Parent { 253 mixin JsonizeMe; 254 @jsonize int x; 255 @jsonize string s; 256 } 257 258 static class Child : Parent { 259 mixin JsonizeMe; 260 @jsonize float f; 261 } 262 263 auto c = new Child; 264 c.x = 5; 265 c.s = "hello"; 266 c.f = 2.1; 267 268 auto json = c.toJSON; 269 assert(json.fromJSON!int("x") == 5); 270 assert(json.fromJSON!string("s") == "hello"); 271 assert(json.fromJSON!float("f").approxEqual(2.1)); 272 273 auto child = json.fromJSON!Child; 274 assert(child !is null); 275 assert(child.x == 5 && child.s == "hello" && child.f.approxEqual(2.1)); 276 277 auto parent = json.fromJSON!Parent; 278 assert(parent.x == 5 && parent.s == "hello"); 279 } 280 281 /// inheritance with ctors 282 unittest { 283 import std.math : approxEqual; 284 static class Parent { 285 mixin JsonizeMe; 286 287 @jsonize this(int x, string s) { 288 _x = x; 289 _s = s; 290 } 291 292 @jsonize @property { 293 int x() { return _x; } 294 string s() { return _s; } 295 } 296 297 private: 298 int _x; 299 string _s; 300 } 301 302 static class Child : Parent { 303 mixin JsonizeMe; 304 305 @jsonize this(int x, string s, float f) { 306 super(x, s); 307 _f = f; 308 } 309 310 @jsonize @property { 311 float f() { return _f; } 312 } 313 314 private: 315 float _f; 316 } 317 318 auto c = new Child(5, "hello", 2.1); 319 320 auto json = c.toJSON; 321 assert(json.fromJSON!int("x") == 5); 322 assert(json.fromJSON!string("s") == "hello"); 323 assert(json.fromJSON!float("f").approxEqual(2.1)); 324 325 auto child = json.fromJSON!Child; 326 assert(child.x == 5 && child.s == "hello" && child.f.approxEqual(2.1)); 327 328 auto parent = json.fromJSON!Parent; 329 assert(parent.x == 5 && parent.s == "hello"); 330 } 331 332 /// renamed members 333 unittest { 334 static class Bleh { 335 mixin JsonizeMe; 336 private { 337 @jsonize("x") int _x; 338 @jsonize("s") string _s; 339 } 340 } 341 342 auto b = new Bleh; 343 b._x = 5; 344 b._s = "blah"; 345 346 auto json = b.toJSON; 347 348 assert(json.fromJSON!int("x") == 5); 349 assert(json.fromJSON!string("s") == "blah"); 350 351 auto reconstruct = json.fromJSON!Bleh; 352 assert(reconstruct._x == b._x && reconstruct._s == b._s); 353 } 354 355 /// type inference 356 unittest { 357 import std.json : parseJSON; 358 import std.string : format; 359 import std.traits : fullyQualifiedName; 360 361 static class TestComponent { 362 mixin JsonizeMe; 363 @jsonize int c; 364 } 365 366 static class TestCompA : TestComponent { 367 mixin JsonizeMe; 368 @jsonize int a; 369 } 370 371 static class TestCompB : TestComponent { 372 mixin JsonizeMe; 373 @jsonize string b; 374 } 375 376 string classKeyA = fullyQualifiedName!TestCompA; 377 string classKeyB = fullyQualifiedName!TestCompB; 378 379 auto data = `[ 380 { 381 "class": "%s", 382 "c": 1, 383 "a": 5 384 }, 385 { 386 "class": "%s", 387 "c": 2, 388 "b": "hello" 389 } 390 ]`.format(classKeyA, classKeyB).parseJSON.fromJSON!(TestComponent[]); 391 392 auto a = cast(TestCompA) data[0]; 393 auto b = cast(TestCompB) data[1]; 394 395 assert(a !is null && a.c == 1 && a.a == 5); 396 assert(b !is null && b.c == 2 && b.b == "hello"); 397 } 398 399 /// type inference with custom type key 400 unittest { 401 import std.string : format; 402 import std.traits : fullyQualifiedName; 403 404 static class TestComponent { 405 mixin JsonizeMe; 406 @jsonize int c; 407 } 408 409 static class TestCompA : TestComponent { 410 mixin JsonizeMe; 411 @jsonize int a; 412 } 413 414 static class TestCompB : TestComponent { 415 mixin JsonizeMe; 416 @jsonize string b; 417 } 418 419 // use "type" instead of "class" to identify dynamic type 420 JsonizeOptions options; 421 options.classKey = "type"; 422 423 // need to use these because unittest is assigned weird name 424 // normally would just be "modulename.classname" 425 string classKeyA = fullyQualifiedName!TestCompA; 426 string classKeyB = fullyQualifiedName!TestCompB; 427 428 auto data = `[ 429 { 430 "type": "%s", 431 "c": 1, 432 "a": 5 433 }, 434 { 435 "type": "%s", 436 "c": 2, 437 "b": "hello" 438 } 439 ]`.format(classKeyA, classKeyB) 440 .fromJSONString!(TestComponent[])(options); 441 442 auto a = cast(TestCompA) data[0]; 443 auto b = cast(TestCompB) data[1]; 444 assert(a !is null && a.c == 1 && a.a == 5); 445 assert(b !is null && b.c == 2 && b.b == "hello"); 446 } 447 448 //test the class map 449 unittest { 450 import std.string : format; 451 import std.traits : fullyQualifiedName; 452 453 static class TestComponent { 454 mixin JsonizeMe; 455 @jsonize int c; 456 } 457 458 static class TestCompA : TestComponent { 459 mixin JsonizeMe; 460 @jsonize int a; 461 } 462 463 static class TestCompB : TestComponent { 464 mixin JsonizeMe; 465 @jsonize string b; 466 } 467 468 // use "type" instead of "class" to identify dynamic type 469 JsonizeOptions options; 470 options.classKey = "type"; 471 472 const string wrongName = "unrelated"; 473 474 string[string] classMap = [ 475 TestCompA.stringof : fullyQualifiedName!TestCompA, 476 TestCompB.stringof : fullyQualifiedName!TestCompB, 477 wrongName : fullyQualifiedName!TestCompA 478 ]; 479 480 options.classMap = delegate string(string rawKey) { 481 if(auto val = rawKey in classMap) 482 return *val; 483 else 484 return null; 485 }; 486 487 auto data = `[ 488 { 489 "type": "%s", 490 "c": 1, 491 "a": 5 492 }, 493 { 494 "type": "%s", 495 "c": 2, 496 "b": "hello" 497 }, 498 { 499 "type": "%s", 500 "c": 3, 501 "a": 12 502 } 503 ]`.format(TestCompA.stringof, TestCompB.stringof, wrongName) 504 .fromJSONString!(TestComponent[])(options); 505 506 auto a = cast(TestCompA) data[0]; 507 auto b = cast(TestCompB) data[1]; 508 auto c = cast(TestCompA) data[2]; 509 assert(a !is null && a.c == 1 && a.a == 5); 510 assert(b !is null && b.c == 2 && b.b == "hello"); 511 assert(c !is null && c.c == 3 && c.a == 12); 512 } 513 514 // Validate issue #20: 515 // Unable to de-jsonize a class when a construct is marked @jsonize. 516 unittest { 517 import std.json : parseJSON; 518 import std.algorithm : canFind; 519 import std.exception : collectException; 520 import jsonizer.common : jsonize; 521 import jsonizer.exceptions : JsonizeConstructorException; 522 import jsonizer.fromjson : fromJSON; 523 import jsonizer.jsonize : JsonizeMe; 524 525 static class A { 526 mixin JsonizeMe; 527 private const int a; 528 529 this(float f) { 530 a = 0; 531 } 532 533 @jsonize this(int a) { 534 this.a = a; 535 } 536 537 @jsonize this(string s, float f) { 538 a = 0; 539 } 540 } 541 542 auto ex = collectException!JsonizeConstructorException(`{}`.parseJSON.fromJSON!A); 543 assert(ex !is null, "failure to match @jsonize'd constructors should throw"); 544 assert(ex.msg.canFind("(int a)") && ex.msg.canFind("(string s, float f)"), 545 "JsonizeConstructorException message should contain attempted constructors"); 546 assert(!ex.msg.canFind("(float f)"), 547 "JsonizeConstructorException message should not contain non-jsonized constructors"); 548 } 549 550 // Validate issue #17: 551 // Unable to construct class containing private (not marked with @jsonize) types. 552 unittest { 553 import std.json : parseJSON; 554 import jsonizer; 555 556 static class A { 557 mixin JsonizeMe; 558 559 private int a; 560 561 @jsonize public this(int a) { 562 this.a = a; 563 } 564 } 565 566 auto json = `{ "a": 5}`.parseJSON; 567 auto a = fromJSON!A(json); 568 569 assert(a.a == 5); 570 } 571 572 // Validate issue #18: 573 // Unable to construct class with const types. 574 unittest { 575 import std.json : parseJSON; 576 import jsonizer; 577 578 static class A { 579 mixin JsonizeMe; 580 581 const int a; 582 583 @jsonize public this(int a) { 584 this.a = a; 585 } 586 } 587 588 auto json = `{ "a": 5}`.parseJSON; 589 auto a = fromJSON!A(json); 590 591 assert(a.a == 5); 592 } 593 594 // Validate issue #19: 595 // Unable to construct class containing private (not marked with @jsonize) types. 596 unittest { 597 import std.json : parseJSON; 598 import jsonizer; 599 600 static class A { 601 mixin JsonizeMe; 602 603 alias Integer = int; 604 Integer a; 605 606 @jsonize public this(Integer a) { 607 this.a = a; 608 } 609 } 610 611 auto json = `{ "a": 5}`.parseJSON; 612 auto a = fromJSON!A(json); 613 614 assert(a.a == 5); 615 } 616 617 unittest { 618 import std.json : parseJSON; 619 import jsonizer; 620 621 static struct A 622 { 623 mixin JsonizeMe; 624 @jsonize int a; 625 @jsonize(Jsonize.opt) string attr; 626 @jsonize(JsonizeIn.opt) string attr2; 627 } 628 629 auto a = A(5); 630 assert(a == a.toJSON.fromJSON!A); 631 assert(a.toJSON == `{ "a":5, "attr2":"" }`.parseJSON); 632 assert(a.toJSON != `{ "a":5, "attr":"", "attr2":"" }`.parseJSON); 633 a.attr = "hello"; 634 assert(a == a.toJSON.fromJSON!A); 635 assert(a.toJSON == `{ "a":5, "attr":"hello", "attr2":"" }`.parseJSON); 636 a.attr2 = "world"; 637 assert(a == a.toJSON.fromJSON!A); 638 assert(a.toJSON == `{ "a":5, "attr":"hello", "attr2":"world" }`.parseJSON); 639 a.attr = ""; 640 assert(a == a.toJSON.fromJSON!A); 641 assert(a.toJSON == `{ "a":5, "attr2":"world" }`.parseJSON); 642 } 643 644 unittest { 645 import std.json : parseJSON; 646 import jsonizer; 647 648 static struct A 649 { 650 mixin JsonizeMe; 651 @jsonize int a; 652 @disable int opEquals( ref const(A) ); 653 } 654 655 static assert(!is(typeof(A.init==A.init))); 656 657 static struct B 658 { 659 mixin JsonizeMe; 660 @jsonize(Jsonize.opt) A a; 661 } 662 663 auto b = B(A(10)); 664 assert(b.a.a == 10); 665 assert(b.a.a == (b.toJSON.fromJSON!B).a.a); 666 assert(b.toJSON == `{"a":{"a":10}}`.parseJSON); 667 b.a.a = 0; 668 assert(b.a.a == int.init ); 669 assert(b.a.a == (b.toJSON.fromJSON!B).a.a); 670 assert(b.toJSON == "{}".parseJSON); 671 } 672 673 unittest { 674 import std.json : parseJSON; 675 import std.exception; 676 import jsonizer; 677 678 static struct T 679 { 680 mixin JsonizeMe; 681 @jsonize(Jsonize.opt) 682 { 683 int a; 684 @jsonize(JsonizeOut.no,JsonizeIn.yes) string b; 685 @jsonize(JsonizeOut.yes,JsonizeIn.no) string c; 686 } 687 } 688 689 auto t = T(5); 690 assertThrown(t.toJSON.fromJSON!T); 691 assert(t.toJSON == `{ "a":5, "c":"" }`.parseJSON); 692 t.b = "hello"; 693 assert(t == `{ "a":5, "b":"hello" }`.parseJSON.fromJSON!T); 694 t.c = "world"; 695 assert(t.toJSON == `{ "a":5, "c":"world" }`.parseJSON); 696 t.a = 0; 697 assert(t.toJSON == `{ "c":"world" }`.parseJSON); 698 auto t2 = `{ "b":"hello", "c":"okda" }`.parseJSON.fromJSON!T; 699 assert(t.a == t2.a); 700 assert(t.b == t2.b); 701 assert(t2.c == ""); 702 } 703 704 // issue 29: Can't have static fields in JsonizeMe type; doesn't compile 705 unittest { 706 import std.json : parseJSON; 707 static class A { 708 mixin JsonizeMe; 709 static string s; 710 static string str() { return "s"; } 711 @jsonize int a; 712 } 713 auto a = new A; 714 a.a = 5; 715 assert(a.toJSON == `{ "a":5 }`.parseJSON); 716 }