1 /** 2 * Contains functions for deserializing JSON data. 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.fromjson; 10 11 import std.json; 12 import std.conv; 13 import std.file; 14 import std.meta; 15 import std.range; 16 import std.traits; 17 import std.string; 18 import std.algorithm; 19 import std.exception; 20 import std.typecons; 21 import jsonizer.exceptions; 22 import jsonizer.common; 23 24 // HACK: this is a hack to allow referencing this particular overload using 25 // &fromJSON!T in the JsonizeMe mixin 26 T _fromJSON(T)(JSONValue json, 27 in ref JsonizeOptions options = JsonizeOptions.defaults) 28 { 29 return fromJSON!T(json, options); 30 } 31 32 /** 33 * Deserialize json into a value of type `T`. 34 * 35 * Params: 36 * T = Target type. can be any primitive/builtin D type, or any 37 * user-defined type using the `JsonizeMe` mixin. 38 * json = `JSONValue` to deserialize. 39 * options = configures the deserialization behavior. 40 */ 41 T fromJSON(T)(JSONValue json, 42 in ref JsonizeOptions options = JsonizeOptions.defaults) 43 { 44 // JSONValue -- identity 45 static if (is(T == JSONValue)) 46 return json; 47 48 // enumeration 49 else static if (is(T == enum)) { 50 enforceJsonType!T(json, JSONType..string); 51 return to!T(json.str); 52 } 53 54 // boolean 55 else static if (is(T : bool)) { 56 if (json.type == JSONType.true_) 57 return true; 58 else if (json.type == JSONType.false_) 59 return false; 60 61 // expected 'true' or 'false' 62 throw new JsonizeTypeException(typeid(bool), json, JSONType.true_, JSONType.false_); 63 } 64 65 // string 66 else static if (is(T : string)) { 67 if (json.type == JSONType.null_) { return null; } 68 enforceJsonType!T(json, JSONType..string); 69 return cast(T) json.str; 70 } 71 72 // numeric 73 else static if (is(T : real)) { 74 switch(json.type) { 75 case JSONType.float_: 76 return cast(T) json.floating; 77 case JSONType.integer: 78 return cast(T) json.integer; 79 case JSONType.uinteger: 80 return cast(T) json.uinteger; 81 case JSONType..string: 82 enforce(json.str.isNumeric, format("tried to extract %s from json string %s", T.stringof, json.str)); 83 return to!T(json.str); // try to parse string as int 84 default: 85 } 86 87 throw new JsonizeTypeException(typeid(bool), json, 88 JSONType.float_, JSONType.integer, JSONType.uinteger, JSONType..string); 89 } 90 91 // array 92 else static if (isArray!T) { 93 if (json.type == JSONType.null_) { return T.init; } 94 enforceJsonType!T(json, JSONType.array); 95 alias ElementType = ForeachType!T; 96 T vals; 97 foreach(idx, val ; json.array) { 98 static if (isStaticArray!T) { 99 vals[idx] = val.fromJSON!ElementType(options); 100 } 101 else { 102 vals ~= val.fromJSON!ElementType(options); 103 } 104 } 105 return vals; 106 } 107 108 // associative array 109 else static if (isAssociativeArray!T) { 110 static assert(is(KeyType!T : string), "toJSON requires string keys for associative array"); 111 if (json.type == JSONType.null_) { return null; } 112 enforceJsonType!T(json, JSONType.object); 113 alias ValType = ValueType!T; 114 T map; 115 foreach(key, val ; json.object) { 116 map[key] = fromJSON!ValType(val, options); 117 } 118 return map; 119 } 120 121 // user-defined class or struct 122 else static if (!isBuiltinType!T) { 123 return fromJSONImpl!T(json, null, options); 124 } 125 126 // by the time we get here, we've tried pretty much everything 127 else { 128 static assert(0, "fromJSON can't handle a " ~ T.stringof); 129 } 130 } 131 132 /// Extract booleans from json values. 133 unittest { 134 assert(JSONValue(false).fromJSON!bool == false); 135 assert(JSONValue(true).fromJSON!bool == true); 136 } 137 138 /// Extract a string from a json string. 139 unittest { 140 assert(JSONValue("asdf").fromJSON!string == "asdf"); 141 } 142 143 /// Extract various numeric types. 144 unittest { 145 assert(JSONValue(1).fromJSON!int == 1); 146 assert(JSONValue(2u).fromJSON!uint == 2u); 147 assert(JSONValue(3.0).fromJSON!double == 3.0); 148 149 // fromJSON accepts numeric strings when a numeric conversion is requested 150 assert(JSONValue("4").fromJSON!long == 4L); 151 } 152 153 /// Convert a json string into an enum value. 154 unittest { 155 enum Category { one, two } 156 assert(JSONValue("one").fromJSON!Category == Category.one); 157 } 158 159 /// Convert a json array into an array. 160 unittest { 161 auto a = [ 1, 2, 3 ]; 162 assert(JSONValue(a).fromJSON!(int[]) == a); 163 } 164 165 /// Convert a json object to an associative array. 166 unittest { 167 auto aa = ["a": 1, "b": 2]; 168 assert(JSONValue(aa).fromJSON!(int[string]) == aa); 169 } 170 171 /// Convert a json object to a user-defined type. 172 /// See the docs for `JsonizeMe` for more detailed examples. 173 unittest { 174 import jsonizer.jsonize; 175 static struct MyStruct { 176 mixin JsonizeMe; 177 178 @jsonize int i; 179 @jsonize string s; 180 float f; 181 } 182 183 auto json = `{ "i": 5, "s": "tally-ho!" }`.parseJSON; 184 auto val = json.fromJSON!MyStruct; 185 assert(val.i == 5); 186 assert(val.s == "tally-ho!"); 187 } 188 189 /** 190 * Extract a value from a json object by its key. 191 * 192 * Throws if `json` is not of `JSONType.object` or the key does not exist. 193 * 194 * Params: 195 * T = Target type. can be any primitive/builtin D type, or any 196 * user-defined type using the `JsonizeMe` mixin. 197 * json = `JSONValue` to deserialize. 198 * key = key of desired value within the object. 199 * options = configures the deserialization behavior. 200 */ 201 T fromJSON(T)(JSONValue json, 202 string key, 203 in ref JsonizeOptions options = JsonizeOptions.defaults) 204 { 205 enforceJsonType!T(json, JSONType.object); 206 enforce(key in json.object, "tried to extract non-existent key " ~ key ~ " from JSONValue"); 207 return fromJSON!T(json.object[key], options); 208 } 209 210 /// Directly extract values from an object by their keys. 211 unittest { 212 auto aa = ["a": 1, "b": 2]; 213 auto json = JSONValue(aa); 214 assert(json.fromJSON!int("a") == 1); 215 assert(json.fromJSON!ulong("b") == 2L); 216 } 217 218 /// Deserialize an instance of a user-defined type from a json object. 219 unittest { 220 import jsonizer.jsonize; 221 222 static struct Foo { 223 mixin JsonizeMe; 224 @jsonize int i; 225 @jsonize string[] a; 226 } 227 228 auto foo = `{"i": 12, "a": [ "a", "b" ]}`.parseJSON.fromJSON!Foo; 229 assert(foo == Foo(12, [ "a", "b" ])); 230 } 231 232 /** 233 * Extract a value from a json object by its key. 234 * 235 * Throws if `json` is not of `JSONType.object`. 236 * Return `defaultVal` if the key does not exist. 237 * 238 * Params: 239 * T = Target type. can be any primitive/builtin D type, or any 240 * user-defined type using the `JsonizeMe` mixin. 241 * json = `JSONValue` to deserialize. 242 * key = key of desired value within the object. 243 * defaultVal = value to return if key is not found 244 * options = configures the deserialization behavior. 245 */ 246 T fromJSON(T)(JSONValue json, 247 string key, 248 T defaultVal, 249 in ref JsonizeOptions options = JsonizeOptions.defaults) 250 { 251 enforceJsonType!T(json, JSONType.object); 252 return (key in json.object) ? fromJSON!T(json.object[key]) : defaultVal; 253 } 254 255 /// Substitute default values when keys aren't present. 256 unittest { 257 auto aa = ["a": 1, "b": 2]; 258 auto json = JSONValue(aa); 259 assert(json.fromJSON!int("a", 7) == 1); 260 assert(json.fromJSON!int("c", 7) == 7); 261 } 262 263 /* 264 * Convert a json value in its string representation into a type `T` 265 * Params: 266 * T = Target type. can be any primitive/builtin D type, or any 267 * user-defined type using the `JsonizeMe` mixin. 268 * json = JSON-formatted string to deserialize. 269 * options = configures the deserialization behavior. 270 */ 271 T fromJSONString(T)(string json, 272 in ref JsonizeOptions options = JsonizeOptions.defaults) 273 { 274 return fromJSON!T(json.parseJSON, options); 275 } 276 277 /// Use `fromJSONString` to parse from a json `string` rather than a `JSONValue` 278 unittest { 279 assert(fromJSONString!(int[])("[1, 2, 3]") == [1, 2, 3]); 280 } 281 282 /** 283 * Read a json-constructable object from a file. 284 * Params: 285 * T = Target type. can be any primitive/builtin D type, or any 286 * user-defined type using the `JsonizeMe` mixin. 287 * path = filesystem path to json file 288 * options = configures the deserialization behavior. 289 */ 290 T readJSON(T)(string path, 291 in ref JsonizeOptions options = JsonizeOptions.defaults) 292 { 293 auto json = parseJSON(readText(path)); 294 return fromJSON!T(json, options); 295 } 296 297 /// Read a json file directly into a specified D type. 298 unittest { 299 import std.path : buildPath; 300 import std.uuid : randomUUID; 301 import std.file : tempDir, write, mkdirRecurse; 302 303 auto dir = buildPath(tempDir(), "jsonizer_readjson_test"); 304 mkdirRecurse(dir); 305 auto file = buildPath(dir, randomUUID().toString); 306 307 file.write("[1, 2, 3]"); 308 309 assert(file.readJSON!(int[]) == [ 1, 2, 3 ]); 310 } 311 312 /** 313 * Read the contents of a json file directly into a `JSONValue`. 314 * Params: 315 * path = filesystem path to json file 316 */ 317 auto readJSON(string path) { 318 return parseJSON(readText(path)); 319 } 320 321 /// Read a json file into a JSONValue. 322 unittest { 323 import std.path : buildPath; 324 import std.uuid : randomUUID; 325 import std.file : tempDir, write, mkdirRecurse; 326 327 auto dir = buildPath(tempDir(), "jsonizer_readjson_test"); 328 mkdirRecurse(dir); 329 auto file = buildPath(dir, randomUUID().toString); 330 331 file.write("[1, 2, 3]"); 332 333 auto json = file.readJSON(); 334 335 assert(json.array[0].integer == 1); 336 assert(json.array[1].integer == 2); 337 assert(json.array[2].integer == 3); 338 } 339 340 deprecated("use fromJSON instead") { 341 /// Deprecated: use `fromJSON` instead. 342 T extract(T)(JSONValue json) { 343 return json.fromJSON!T; 344 } 345 } 346 347 private: 348 void enforceJsonType(T)(JSONValue json, JSONType[] expected ...) { 349 if (!expected.canFind(json.type)) { 350 throw new JsonizeTypeException(typeid(T), json, expected); 351 } 352 } 353 354 unittest { 355 import std.exception : assertThrown, assertNotThrown; 356 with (JSONType) { 357 assertThrown!JsonizeTypeException(enforceJsonType!int(JSONValue("hi"), integer, uinteger)); 358 assertThrown!JsonizeTypeException(enforceJsonType!(bool[string])(JSONValue([ 5 ]), object)); 359 assertNotThrown(enforceJsonType!int(JSONValue(5), integer, uinteger)); 360 assertNotThrown(enforceJsonType!(bool[string])(JSONValue(["key": true]), object)); 361 } 362 } 363 364 // Internal implementation of fromJSON for user-defined types 365 // If T is a nested class, pass the parent of type P 366 // otherwise pass null for the parent 367 T fromJSONImpl(T, P)(JSONValue json, P parent, in ref JsonizeOptions options) { 368 static if (is(typeof(null) : T)) { 369 if (json.type == JSONType.null_) { return null; } 370 } 371 372 // try constructing from a primitive type using a single-param constructor 373 if (json.type != JSONType.object) { 374 return invokePrimitiveCtor!T(json, parent); 375 } 376 377 static if (!isNested!T && is(T == class)) 378 { 379 // if the class is identified in the json, construct an instance of the 380 // specified type 381 auto className = json.fromJSON!string(options.classKey, null); 382 if (options.classMap) { 383 if(auto tmp = options.classMap(className)) 384 className = tmp; 385 } 386 if (className && className != fullyQualifiedName!T) { 387 auto handler = className in T._jsonizeCtors; 388 assert(handler, className ~ " not registered in " ~ T.stringof); 389 JsonizeOptions newopts = options; 390 newopts.classKey = null; // don't recursively loop looking up class name 391 return (*handler)(json, newopts); 392 } 393 } 394 395 // next, try to find a contructor marked with @jsonize and call that 396 static if (__traits(hasMember, T, "__ctor")) { 397 alias Overloads = AliasSeq!(__traits(getOverloads, T, "__ctor")); 398 foreach(overload ; Overloads) { 399 static if (staticIndexOf!(jsonize, __traits(getAttributes, overload)) >= 0) { 400 if (canSatisfyCtor!overload(json)) { 401 return invokeCustomJsonCtor!(T, overload)(json, parent); 402 } 403 } 404 } 405 406 // no constructor worked, is default-construction an option? 407 static if(!hasDefaultCtor!T) { 408 // not default-constructable, need to fail here 409 alias ctors = Filter!(isJsonized, __traits(getOverloads, T, "__ctor")); 410 JsonizeConstructorException.doThrow!(T, ctors)(json); 411 } 412 } 413 414 // if no @jsonized ctor, try to use a default ctor and populate the fields 415 static if(hasDefaultCtor!T) { 416 return invokeDefaultCtor!T(json, parent, options); 417 } 418 419 assert(0, "all attempts at deserializing " ~ fullyQualifiedName!T ~ " failed."); 420 } 421 422 // return true if keys can satisfy parameter names 423 bool canSatisfyCtor(alias Ctor)(JSONValue json) { 424 import std.meta : aliasSeqOf; 425 auto obj = json.object; 426 alias Params = ParameterIdentifierTuple!Ctor; 427 alias Types = ParameterTypeTuple!Ctor; 428 alias Defaults = ParameterDefaultValueTuple!Ctor; 429 foreach(i ; aliasSeqOf!(Params.length.iota)) { 430 if (Params[i] !in obj && typeid(Defaults[i]) == typeid(void)) { 431 return false; // param had no default value and was not specified 432 } 433 } 434 return true; 435 } 436 437 T invokeCustomJsonCtor(T, alias Ctor, P)(JSONValue json, P parent) { 438 import std.meta : aliasSeqOf; 439 enum params = ParameterIdentifierTuple!(Ctor); 440 alias defaults = ParameterDefaultValueTuple!(Ctor); 441 alias Types = ParameterTypeTuple!(Ctor); 442 Tuple!(Types) args; 443 foreach(i ; aliasSeqOf!(params.length.iota)) { 444 enum paramName = params[i]; 445 if (paramName in json.object) { 446 args[i] = json.object[paramName].fromJSON!(Types[i]); 447 } 448 else { // no value specified in json 449 static if (is(defaults[i] == void)) { 450 enforce(0, "parameter " ~ paramName ~ " has no default value and was not specified"); 451 } 452 else { 453 args[i] = defaults[i]; 454 } 455 } 456 } 457 static if (isNested!T) 458 return parent..new T(args.expand); 459 else static if (is(T == class)) 460 return new T(args.expand); 461 else 462 return T(args.expand); 463 } 464 465 T invokeDefaultCtor(T, P)(JSONValue json, P parent, JsonizeOptions options) { 466 T obj; 467 468 static if (isNested!T) { 469 obj = parent..new T; 470 } 471 else static if (is(T == struct)) { 472 obj = T.init; 473 } 474 else { 475 obj = new T; 476 } 477 478 populate(obj, json, options); 479 return obj; 480 } 481 482 alias isJsonized(alias member) = hasUDA!(member, jsonize); 483 484 unittest { 485 static class Foo { 486 @jsonize int i; 487 @jsonize("s") string _s; 488 @jsonize @property string sprop() { return _s; } 489 @jsonize @property void sprop(string str) { _s = str; } 490 float f; 491 @property int iprop() { return i; } 492 493 @jsonize this(string s) { } 494 this(int i) { } 495 } 496 497 Foo f; 498 static assert(isJsonized!(__traits(getMember, f, "i"))); 499 static assert(isJsonized!(__traits(getMember, f, "_s"))); 500 static assert(isJsonized!(__traits(getMember, f, "sprop"))); 501 static assert(isJsonized!(__traits(getMember, f, "i"))); 502 static assert(!isJsonized!(__traits(getMember, f, "f"))); 503 static assert(!isJsonized!(__traits(getMember, f, "iprop"))); 504 505 import std.typetuple : Filter; 506 static assert(Filter!(isJsonized, __traits(getOverloads, Foo, "__ctor")).length == 1); 507 } 508 509 T invokePrimitiveCtor(T, P)(JSONValue json, P parent) { 510 static if (__traits(hasMember, T, "__ctor")) { 511 foreach(overload ; __traits(getOverloads, T, "__ctor")) { 512 alias Types = ParameterTypeTuple!overload; 513 514 // look for an @jsonized ctor with a single parameter 515 static if (hasUDA!(overload, jsonize) && Types.length == 1) { 516 return construct!T(parent, json.fromJSON!(Types[0])); 517 } 518 } 519 } 520 521 assert(0, "No primitive ctor for type " ~ T.stringof); 522 } 523 524 void populate(T)(ref T obj, JSONValue json, in JsonizeOptions opt) { 525 string[] missingKeys; 526 uint fieldsFound = 0; 527 528 foreach (member ; T._membersWithUDA!jsonize) { 529 string key = jsonKey!(T, member); 530 531 auto required = JsonizeIn.unspecified; 532 foreach (attr ; T._getUDAs!(member, jsonize)) 533 if (attr.perform_in != JsonizeIn.unspecified) 534 required = attr.perform_in; 535 536 if (required == JsonizeIn.no) continue; 537 538 if (auto jsonval = key in json.object) { 539 ++fieldsFound; 540 alias MemberType = T._writeMemberType!member; 541 542 static if (!is(MemberType == void)) { 543 static if (isAggregateType!MemberType && isNested!MemberType) 544 auto val = fromJSONImpl!Inner(*jsonval, obj, opt); 545 else { 546 auto val = fromJSON!MemberType(*jsonval, opt); 547 } 548 549 obj._writeMember!(MemberType, member)(val); 550 } 551 } 552 else { 553 if (required == JsonizeIn.yes) missingKeys ~= key; 554 } 555 } 556 557 string[] extraKeys; 558 if (!T._jsonizeIgnoreExtra && fieldsFound < json.object.length) 559 extraKeys = json.object.keys.filter!(x => x.isUnknownKey!T).array; 560 561 if (missingKeys.length > 0 || extraKeys.length > 0) 562 throw new JsonizeMismatchException(typeid(T), extraKeys, missingKeys); 563 } 564 565 bool isUnknownKey(T)(string key) { 566 foreach (member ; T._membersWithUDA!jsonize) 567 if (jsonKey!(T, member) == key) 568 return false; 569 570 return true; 571 } 572 573 T construct(T, P, Params ...)(P parent, Params params) { 574 static if (!is(P == typeof(null))) { 575 return parent..new T(params); 576 } 577 else static if (is(typeof(T(params)) == T)) { 578 return T(params); 579 } 580 else static if (is(typeof(new T(params)) == T)) { 581 return new T(params); 582 } 583 else { 584 static assert(0, "Cannot construct"); 585 } 586 } 587 588 unittest { 589 static struct Foo { 590 this(int i) { this.i = i; } 591 int i; 592 } 593 594 assert(construct!Foo(null).i == 0); 595 assert(construct!Foo(null, 4).i == 4); 596 assert(!__traits(compiles, construct!Foo("asd"))); 597 } 598 599 unittest { 600 static class Foo { 601 this(int i) { this.i = i; } 602 603 this(int i, string s) { 604 this.i = i; 605 this.s = s; 606 } 607 608 int i; 609 string s; 610 } 611 612 assert(construct!Foo(null, 4).i == 4); 613 assert(construct!Foo(null, 4, "asdf").s == "asdf"); 614 assert(!__traits(compiles, construct!Foo("asd"))); 615 } 616 617 unittest { 618 class Foo { 619 class Bar { 620 int i; 621 this(int i) { this.i = i; } 622 } 623 } 624 625 auto f = new Foo; 626 assert(construct!(Foo.Bar)(f, 2).i == 2); 627 } 628 629 template hasDefaultCtor(T) { 630 static if (isNested!T) { 631 alias P = typeof(__traits(parent, T).init); 632 enum hasDefaultCtor = is(typeof(P.init..new T()) == T); 633 } 634 else { 635 enum hasDefaultCtor = is(typeof(T()) == T) || is(typeof(new T()) == T); 636 } 637 } 638 639 version(unittest) { 640 struct S1 { } 641 struct S2 { this(int i) { } } 642 struct S3 { @disable this(); } 643 644 class C1 { } 645 class C2 { this(string s) { } } 646 class C3 { class Inner { } } 647 class C4 { class Inner { this(int i); } } 648 } 649 650 unittest { 651 static assert( hasDefaultCtor!S1); 652 static assert( hasDefaultCtor!S2); 653 static assert(!hasDefaultCtor!S3); 654 655 static assert( hasDefaultCtor!C1); 656 static assert(!hasDefaultCtor!C2); 657 658 static assert( hasDefaultCtor!C3); 659 static assert( hasDefaultCtor!(C3.Inner)); 660 static assert(!hasDefaultCtor!(C4.Inner)); 661 } 662 663 template hasCustomJsonCtor(T) { 664 static if (__traits(hasMember, T, "__ctor")) { 665 alias Overloads = AliasSeq!(__traits(getOverloads, T, "__ctor")); 666 667 enum test(alias fn) = staticIndexOf!(jsonize, __traits(getAttributes, fn)) >= 0; 668 669 enum hasCustomJsonCtor = anySatisfy!(test, Overloads); 670 } 671 else { 672 enum hasCustomJsonCtor = false; 673 } 674 } 675 676 unittest { 677 static struct S1 { } 678 static struct S2 { this(int i); } 679 static struct S3 { @jsonize this(int i); } 680 static struct S4 { this(float f); @jsonize this(int i); } 681 682 static assert(!hasCustomJsonCtor!S1); 683 static assert(!hasCustomJsonCtor!S2); 684 static assert( hasCustomJsonCtor!S3); 685 static assert( hasCustomJsonCtor!S4); 686 687 static class C1 { } 688 static class C2 { this() {} } 689 static class C3 { @jsonize this() {} } 690 static class C4 { @jsonize this(int i); this(float f); } 691 692 static assert(!hasCustomJsonCtor!C1); 693 static assert(!hasCustomJsonCtor!C2); 694 static assert( hasCustomJsonCtor!C3); 695 static assert( hasCustomJsonCtor!C4); 696 }