1 /* 2 * Hunt - A refined core library for D programming language. 3 * 4 * Copyright (C) 2018-2019 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.serialization.JsonSerializer; 13 14 import hunt.serialization.Common; 15 import hunt.logging.ConsoleLogger; 16 import hunt.util.Common; 17 18 import std.algorithm : map; 19 import std.array; 20 import std.conv; 21 import std.datetime; 22 import std.json; 23 import std.stdio; 24 import std.traits; 25 26 27 enum MetaTypeName = "__metatype__"; 28 29 /* -------------------------------------------------------------------------- */ 30 /* Annotations */ 31 /* -------------------------------------------------------------------------- */ 32 33 // https://github.com/FasterXML/jackson-annotations 34 // http://tutorials.jenkov.com/java-json/jackson-annotations.html 35 36 enum JsonIgnore; 37 38 struct JsonProperty { 39 string name; 40 } 41 42 /** 43 * 44 */ 45 interface JsonSerializable { 46 47 JSONValue jsonSerialize(); 48 49 void jsonDeserialize(const(JSONValue) value); 50 } 51 52 53 /** 54 * 55 */ 56 final class JsonSerializer { 57 58 static T getItemAs(T, bool canThrow = false)(ref const(JSONValue) json, string name, 59 T defaultValue = T.init) if (!is(T == void)) { 60 if (json.type() != JSONType.object) { 61 return handleException!(T, canThrow)(json, "wrong member type", defaultValue); 62 } 63 64 auto item = name in json; 65 if (item is null) { 66 return handleException!(T, canThrow)(json, "wrong member type", defaultValue); 67 } 68 else { 69 return toObject!T(*item); // , defaultValue 70 } 71 } 72 73 static T toObject(T, TraverseBase traverseBase = TraverseBase.yes, bool canThrow = false) 74 (string json, T defaultValue = T.init) if (is(T == class)) { 75 return toObject!(T, traverseBase, canThrow)(parseJSON(json)); 76 } 77 78 static T toObject(T, bool canThrow = false) 79 (string json, T defaultValue = T.init) if (!is(T == class)) { 80 return toObject!(T, canThrow)(parseJSON(json)); 81 } 82 83 /** 84 * Converts a `JSONValue` to an object of type `T` by filling its fields with the JSON's fields. 85 */ 86 static T toObject(T, TraverseBase traverseBase = TraverseBase.yes, bool canThrow = false) 87 (auto ref const(JSONValue) json, T defaultValue = T.init) 88 if (is(T == class) && __traits(compiles, new T())) { // is(typeof(new T())) 89 90 if (json.type() != JSONType.object) { 91 return handleException!(T, canThrow)(json, "wrong object type", defaultValue); 92 } 93 94 auto result = new T(); 95 96 static if(is(T : JsonSerializable)) { 97 result.jsonDeserialize(json); 98 } else { 99 try { 100 deserializeObject!(T, traverseBase)(result, json); 101 } catch (JSONException e) { 102 return handleException!(T, canThrow)(json, e.msg, defaultValue); 103 } 104 } 105 106 return result; 107 } 108 109 /** 110 * 111 */ 112 static T toObject(T, bool canThrow = false)( 113 auto ref const(JSONValue) json, T defaultValue = T.init) 114 if (is(T == struct) && !is(T == SysTime)) { 115 116 JSONType jt = json.type(); 117 118 if (jt != JSONType.object) { 119 return handleException!(T, canThrow)(json, "wrong object type", defaultValue); 120 } 121 122 auto result = T(); 123 124 try { 125 static foreach (string member; FieldNameTuple!T) { 126 deserializeMember!(member, TraverseBase.no)(result, json); 127 } 128 } catch (JSONException e) { 129 return handleException!(T, canThrow)(json, e.msg, defaultValue); 130 } 131 132 return result; 133 } 134 135 136 static void deserializeObject(T)(ref T target, auto ref const(JSONValue) json) 137 if(is(T == struct)) { 138 139 static foreach (string member; FieldNameTuple!T) { 140 // current fields 141 deserializeMember!(member, TraverseBase.no)(target, json); 142 } 143 } 144 145 /** 146 */ 147 static void deserializeObject(T, TraverseBase traverseBase = TraverseBase.yes) 148 (T target, auto ref const(JSONValue) json) if(is(T == class)) { 149 150 static foreach (string member; FieldNameTuple!T) { 151 // current fields 152 deserializeMember!(member, traverseBase)(target, json); 153 } 154 155 // super fields 156 static if(traverseBase) { 157 alias baseClasses = BaseClassesTuple!T; 158 alias BaseType = baseClasses[0]; 159 160 static if(baseClasses.length >= 1 && !is(BaseType == Object)) { 161 debug(HUNT_DEBUG_MORE) { 162 infof("TODO: deserializing fields in base %s for %s", BaseType.stringof, T.stringof); 163 } 164 auto jsonItemPtr = "super" in json; 165 if(jsonItemPtr !is null) { 166 deserializeObject!(BaseType, traverseBase)(target, *jsonItemPtr); 167 } 168 } 169 } 170 } 171 172 private static void deserializeMember(string member, TraverseBase traverseBase = TraverseBase.yes, T) 173 (ref T target, auto ref const(JSONValue) json) { 174 175 alias currentMember = __traits(getMember, T, member); 176 alias memberType = typeof(currentMember); 177 debug(HUNT_DEBUG_MORE) { 178 infof("deserializing member: %s %s", memberType.stringof, member); 179 } 180 181 static if(hasUDA!(currentMember, Ignore) || hasUDA!(currentMember, JsonIgnore)) { 182 version(HUNT_DEBUG) { 183 infof("Ignore a member: %s %s", memberType.stringof, member); 184 } 185 } else { 186 static if(is(memberType == interface) && !is(memberType : JsonSerializable)) { 187 version(HUNT_DEBUG) warning("skipped a interface member (not XmlSerializable): " ~ member); 188 } else { 189 190 alias jsonPropertyUDAs = getUDAs!(currentMember, JsonProperty); 191 static if(jsonPropertyUDAs.length > 0) { 192 enum PropertyName = jsonPropertyUDAs[0].name; 193 enum jsonKeyName = (PropertyName.length == 0) ? member : PropertyName; 194 } else { 195 enum jsonKeyName = member; 196 } 197 198 auto jsonItemPtr = jsonKeyName in json; 199 if(jsonItemPtr is null) { 200 version(HUNT_DEBUG) { 201 if(jsonKeyName != member) 202 warningf("No data available for member: %s as %s", member, jsonKeyName); 203 else 204 warningf("No data available for member: %s", member); 205 } 206 } else { 207 debug(HUNT_DEBUG_MORE) tracef("available data: %s = %s", member, jsonItemPtr.toString()); 208 static if(is(memberType == class)) { 209 __traits(getMember, target, member) = toObject!(memberType)(*jsonItemPtr); 210 } else { 211 __traits(getMember, target, member) = toObject!(memberType, false)(*jsonItemPtr); 212 } 213 } 214 } 215 } 216 } 217 218 static T toObject(T, bool canThrow = false)( 219 auto ref const(JSONValue) json, 220 T defaultValue = T.init) 221 if(is(T == interface) && is(T : JsonSerializable)) { 222 223 auto jsonItemPtr = MetaTypeName in json; 224 if(jsonItemPtr is null) { 225 warningf("Can't find 'type' item for interface %s", T.stringof); 226 return T.init; 227 } 228 string typeId = jsonItemPtr.str; 229 T t = cast(T) Object.factory(typeId); 230 if(t is null) { 231 warningf("Can't create instance for %s", T.stringof); 232 } 233 t.jsonDeserialize(json); 234 return t; 235 236 } 237 238 static T toObject(T, bool canThrow = false)( 239 auto ref const(JSONValue) json, 240 T defaultValue = T.init) 241 if(is(T == SysTime)) { 242 243 JSONType jt = json.type(); 244 if(jt == JSONType..string) { 245 return SysTime.fromSimpleString(json.str); 246 } else if(jt == JSONType.integer) { 247 return SysTime(json.integer); // STD time 248 } else { 249 return handleException!(T, canThrow)(json, "wrong SysTime type", defaultValue); 250 } 251 } 252 253 // static N toObject(N : Nullable!T, T, bool canThrow = false)(auto ref const(JSONValue) json) { 254 255 // return (json.type == JSONType.null_) ? N() : toObject!T(json).nullable; 256 // } 257 258 static T toObject(T : JSONValue, bool canThrow = false)(auto ref const(JSONValue) json) { 259 import std.typecons : nullable; 260 return json.nullable.get(); 261 } 262 263 static T toObject(T, bool canThrow = false) 264 (auto ref const(JSONValue) json, T defaultValue = T.init) 265 if (isNumeric!T || isSomeChar!T) { 266 267 switch (json.type) { 268 case JSONType.null_, JSONType.false_: 269 return 0.to!T; 270 271 case JSONType.true_: 272 return 1.to!T; 273 274 case JSONType.float_: 275 return json.floating.to!T; 276 277 case JSONType.integer: 278 return json.integer.to!T; 279 280 case JSONType.uinteger: 281 return json.uinteger.to!T; 282 283 case JSONType..string: 284 try { 285 return json.str.to!T; 286 } catch(Exception ex) { 287 return handleException!(T, canThrow)(json, ex.msg, defaultValue); 288 } 289 290 default: 291 return handleException!(T, canThrow)(json, "", defaultValue); 292 } 293 } 294 295 static T handleException(T, bool canThrow = false) (auto ref const(JSONValue) json, 296 string message, T defaultValue = T.init) { 297 static if (canThrow) { 298 throw new JSONException(json.toString() ~ " is not a " ~ T.stringof ~ " type"); 299 } else { 300 version (HUNT_DEBUG) 301 warningf(" %s is not a %s type. Using the defaults instead! \n Exception: %s", 302 json.toString(), T.stringof, message); 303 return defaultValue; 304 } 305 } 306 307 static T toObject(T, bool canThrow = false) 308 (auto ref const(JSONValue) json) if (isBoolean!T) { 309 310 switch (json.type) { 311 case JSONType.null_, JSONType.false_: 312 return false; 313 314 case JSONType.float_: 315 return json.floating != 0; 316 317 case JSONType.integer: 318 return json.integer != 0; 319 320 case JSONType.uinteger: 321 return json.uinteger != 0; 322 323 case JSONType..string: 324 return json.str.length > 0; 325 326 default: 327 return true; 328 } 329 } 330 331 static T toObject(T, bool canThrow = false)(auto ref const(JSONValue) json, T defaultValue = T.init) 332 if (isSomeString!T || is(T : string) || is(T : wstring) || is(T : dstring)) { 333 334 static if (is(T == enum)) { 335 foreach (member; __traits(allMembers, T)) { 336 auto m = __traits(getMember, T, member); 337 338 if (json.str == m) { 339 return m; 340 } 341 } 342 return handleException!(T, canThrow)(json, 343 " is not a member of " ~ typeid(T).toString(), defaultValue); 344 } else { 345 return (json.type == JSONType..string ? json.str : json.toString()).to!T; 346 } 347 } 348 349 static T toObject(T : U[], bool canThrow = false, U) 350 (auto ref const(JSONValue) json, 351 T defaultValue = T.init) 352 if (isArray!T && !isSomeString!T && !is(T : string) && !is(T 353 : wstring) && !is(T : dstring)) { 354 355 switch (json.type) { 356 case JSONType.null_: 357 return []; 358 359 case JSONType.false_: 360 return [toObject!U(JSONValue(false))]; 361 362 case JSONType.true_: 363 return [toObject!U(JSONValue(true))]; 364 365 case JSONType.array: 366 return json.array 367 .map!(value => toObject!U(value)) 368 .array 369 .to!T; 370 371 case JSONType.object: 372 return handleException!(T, canThrow)(json, "", defaultValue); 373 374 default: 375 return [toObject!U(json)]; 376 } 377 } 378 379 static T toObject(T : U[K], bool canThrow = false, U, K)( 380 auto ref const(JSONValue) json, T defaultValue = T.init) 381 if (isAssociativeArray!T) { 382 383 U[K] result; 384 385 switch (json.type) { 386 case JSONType.null_: 387 return result; 388 389 case JSONType.object: 390 foreach (key, value; json.object) { 391 result[key.to!K] = toObject!U(value); 392 } 393 394 break; 395 396 case JSONType.array: 397 foreach (key, value; json.array) { 398 result[key.to!K] = toObject!U(value); 399 } 400 401 break; 402 403 default: 404 return handleException!(T, canThrow)(json, "", defaultValue); 405 } 406 407 return result; 408 } 409 410 411 /* -------------------------------------------------------------------------- */ 412 /* toJson */ 413 /* -------------------------------------------------------------------------- */ 414 415 /** 416 * class 417 */ 418 static JSONValue toJson(int depth=-1, T)(T value) if (is(T == class)) { 419 enum options = SerializationOptions().depth(depth); 420 return toJson!(options)(value); 421 } 422 423 424 /// ditto 425 static JSONValue toJson(SerializationOptions options, T) 426 (T value) if (is(T == class)) { 427 428 debug(HUNT_DEBUG_MORE) { 429 info("======== current type: class " ~ T.stringof); 430 tracef("%s, T: %s", 431 options, T.stringof); 432 } 433 static if(is(T : JsonSerializable)) { 434 // JsonSerializable first 435 return toJson!(JsonSerializable, IncludeMeta.no)(value); 436 } else { 437 return serializeObject!(options, T)(value); 438 } 439 } 440 441 deprecated("Using the other form of toJson(options) instead.") 442 static JSONValue toJson(T, TraverseBase traverseBase, 443 OnlyPublic onlyPublic = OnlyPublic.no, 444 IncludeMeta includeMeta = IncludeMeta.no) 445 (T value) if (is(T == class)) { 446 enum options = SerializationOptions(onlyPublic, traverseBase, includeMeta); 447 return serializeObject!(options, T)(value); 448 } 449 450 451 deprecated("Using serializeObject(SerializationOptions) instead.") 452 static JSONValue serializeObject(OnlyPublic onlyPublic, TraverseBase traverseBase, 453 IncludeMeta includeMeta, T) (T value) if (is(T == class)) { 454 455 enum options = SerializationOptions(onlyPublic, traverseBase, includeMeta); 456 return serializeObject!(options, T)(value); 457 } 458 459 /** 460 * class object 461 */ 462 static JSONValue serializeObject(SerializationOptions options = SerializationOptions.Full, T) 463 (T value) if (is(T == class)) { 464 import std.traits : isSomeFunction, isType; 465 466 debug(HUNT_DEBUG_MORE) { 467 info("======== current type: class " ~ T.stringof); 468 tracef("%s, T: %s", options, T.stringof); 469 // tracef("traverseBase = %s, onlyPublic = %s, includeMeta = %s, T: %s", 470 // traverseBase, onlyPublic, includeMeta, T.stringof); 471 } 472 473 if (value is null) { 474 version(HUNT_DEBUG) warning("value is null"); 475 return JSONValue(null); 476 } 477 478 auto result = JSONValue(); 479 static if(options.includeMeta) { 480 result[MetaTypeName] = typeid(T).name; 481 } 482 // debug(HUNT_DEBUG_MORE) pragma(msg, "======== current type: class " ~ T.stringof); 483 484 // super fields 485 static if(options.traverseBase) { 486 alias baseClasses = BaseClassesTuple!T; 487 static if(baseClasses.length >= 1) { 488 489 alias BaseType = baseClasses[0]; 490 debug(HUNT_DEBUG_MORE) { 491 tracef("BaseType: %s", BaseType.stringof); 492 } 493 static if(!is(BaseType == Object)) { 494 JSONValue superResult = serializeObject!(options, BaseType)(value); 495 if(!superResult.isNull) 496 result["super"] = superResult; 497 } 498 } 499 } 500 501 // current fields 502 static foreach (string member; FieldNameTuple!T) { 503 serializeMember!(member, options)(value, result); 504 } 505 506 return result; 507 } 508 509 /** 510 * struct 511 */ 512 static JSONValue toJson(SerializationOptions options = SerializationOptions(), T)(T value) 513 if (is(T == struct) && !is(T == SysTime)) { 514 515 static if(is(T == JSONValue)) { 516 return value; 517 } else { 518 auto result = JSONValue(); 519 // debug(HUNT_DEBUG_MORE) pragma(msg, "======== current type: struct " ~ T.stringof); 520 debug(HUNT_DEBUG_MORE) info("======== current type: struct " ~ T.stringof); 521 522 static foreach (string member; FieldNameTuple!T) { 523 serializeMember!(member, options)(value, result); 524 } 525 526 return result; 527 } 528 } 529 530 /** 531 * Object's memeber 532 */ 533 private static void serializeMember(string member, 534 SerializationOptions options = SerializationOptions.Default, T) 535 (T obj, ref JSONValue result) { 536 537 // debug(HUNT_DEBUG_MORE) pragma(msg, "\tfield=" ~ member); 538 539 alias currentMember = __traits(getMember, T, member); 540 541 static if(options.onlyPublic) { 542 static if (__traits(getProtection, currentMember) == "public") { 543 enum canSerialize = true; 544 } else { 545 enum canSerialize = false; 546 } 547 } else static if(hasUDA!(currentMember, Ignore) || hasUDA!(currentMember, JsonIgnore)) { 548 enum canSerialize = false; 549 } else { 550 enum canSerialize = true; 551 } 552 553 debug(HUNT_DEBUG_MORE) { 554 tracef("name: %s, %s", member, options); 555 } 556 557 static if(canSerialize) { 558 alias memberType = typeof(currentMember); 559 debug(HUNT_DEBUG_MORE) infof("memberType: %s in %s", memberType.stringof, T.stringof); 560 561 static if(is(memberType == interface) && !is(memberType : JsonSerializable)) { 562 version(HUNT_DEBUG) warning("skipped a interface member(not JsonSerializable): " ~ member); 563 } else { 564 auto m = __traits(getMember, obj, member); 565 566 alias jsonPropertyUDAs = getUDAs!(currentMember, JsonProperty); 567 static if(jsonPropertyUDAs.length > 0) { 568 enum PropertyName = jsonPropertyUDAs[0].name; 569 enum jsonKeyName = (PropertyName.length == 0) ? member : PropertyName; 570 } else { 571 enum jsonKeyName = member; 572 } 573 574 auto json = serializeMember!(options)(m); 575 576 debug(HUNT_DEBUG_MORE) { 577 tracef("name: %s, value: %s", member, json.toString()); 578 } 579 580 bool canSetValue = true; 581 if(json.isNull) { 582 static if(options.ignoreNull) { 583 canSetValue = false; 584 } 585 } 586 587 if (canSetValue) { 588 // trace(result); 589 if(!result.isNull) { 590 auto jsonItemPtr = jsonKeyName in result; 591 if(jsonItemPtr !is null) { 592 version(HUNT_DEBUG) warning("overrided field: " ~ member); 593 } 594 } 595 result[jsonKeyName] = json; 596 } 597 } 598 } else { 599 debug(HUNT_DEBUG_MORE) tracef("skipped member, name: %s", member); 600 } 601 } 602 603 private static JSONValue serializeMember(SerializationOptions options, T)(T m) { 604 JSONValue json; 605 enum depth = options.depth; 606 static if(is(T == interface) && is(T : JsonSerializable)) { 607 static if(depth == -1 || depth > 0) { json = toJson!(JsonSerializable)(m);} 608 } else static if(is(T == SysTime)) { 609 json = toJson!SysTime(m); 610 // } else static if(isSomeString!T) { 611 // json = toJson(m); 612 } else static if(is(T == class)) { 613 if(m !is null) { 614 json = serializeObjectMember!(options)(m); 615 } 616 } else static if(is(T == struct)) { 617 json = serializeObjectMember!(options)(m); 618 } else static if(is(T : U[], U)) { 619 if(m is null) { 620 static if(!options.ignoreNull) { 621 static if(isSomeString!T) { 622 json = toJson(m); 623 } else { 624 json = JSONValue[].init; 625 } 626 } 627 } else { 628 static if (is(U == class) || is(U == struct) || is(U == interface)) { 629 // class[] obj; struct[] obj; 630 json = serializeObjectMember!(options)(m); 631 } else { 632 json = toJson(m); 633 } 634 } 635 } else { 636 json = toJson(m); 637 } 638 639 return json; 640 641 } 642 643 private static JSONValue serializeObjectMember(SerializationOptions options = 644 SerializationOptions.Default, T)(ref T m) { 645 enum depth = options.depth; 646 static if(depth > 0) { 647 enum SerializationOptions memeberOptions = options.depth(options.depth-1); 648 return toJson!(memeberOptions)(m); 649 } else static if(depth == -1) { 650 return toJson!(options)(m); 651 } else { 652 return JSONValue.init; 653 } 654 } 655 656 /** 657 * SysTime 658 */ 659 static JSONValue toJson(T)(T value, bool asInteger=true) if(is(T == SysTime)) { 660 if(asInteger) 661 return JSONValue(value.stdTime()); // STD time 662 else 663 return JSONValue(value.toString()); 664 } 665 666 /** 667 * JsonSerializable 668 */ 669 static JSONValue toJson(T, IncludeMeta includeMeta = IncludeMeta.yes) 670 (T value) if (is(T == interface) && is(T : JsonSerializable)) { 671 debug(HUNT_DEBUG_MORE) { 672 infof("======== current type: interface = %s, Object = %s", 673 T.stringof, typeid(cast(Object)value).name); 674 } 675 676 JSONValue v = value.jsonSerialize(); 677 static if(includeMeta) { 678 auto itemPtr = MetaTypeName in v; 679 if(itemPtr is null) 680 v[MetaTypeName] = typeid(cast(Object)value).name; 681 } 682 // TODO: Tasks pending completion -@zhangxueping at 2019-09-28T07:45:09+08:00 683 // remove the MetaTypeName memeber 684 debug(HUNT_DEBUG_MORE) trace(v.toString()); 685 return v; 686 } 687 688 /** 689 * Basic type 690 */ 691 static JSONValue toJson(T)(T value) if (isBasicType!T) { 692 return JSONValue(value); 693 } 694 695 /** 696 * string[] 697 */ 698 static JSONValue toJson(T)(T value) 699 if (is(T : U[], U) && (isBasicType!U || isSomeString!U)) { 700 return JSONValue(value); 701 } 702 703 /** 704 * class[] 705 */ 706 static JSONValue toJson(SerializationOptions options = SerializationOptions.Normal, 707 T : U[], U) (T value) if(is(T : U[], U) && is(U == class)) { 708 if(value is null) { 709 return JSONValue(JSONValue[].init); 710 } else { 711 return JSONValue(value.map!(item => toJson!(options)(item))() 712 .map!(json => json.isNull ? JSONValue(null) : json).array); 713 } 714 } 715 716 /** 717 * struct[] 718 */ 719 static JSONValue toJson(SerializationOptions options = SerializationOptions.Normal, 720 T : U[], U)(T value) if(is(U == struct)) { 721 if(value is null) { 722 return JSONValue(JSONValue[].init); 723 } else { 724 static if(is(U == SysTime)) { 725 return JSONValue(value.map!(item => toJson(item))() 726 .map!(json => json.isNull ? JSONValue(null) : json).array); 727 } else { 728 return JSONValue(value.map!(item => toJson!(options)(item))() 729 .map!(json => json.isNull ? JSONValue(null) : json).array); 730 } 731 } 732 } 733 734 /** 735 * V[K] 736 */ 737 static JSONValue toJson(SerializationOptions options = SerializationOptions.Normal, 738 T : V[K], V, K)(T value) { 739 auto result = JSONValue(); 740 741 foreach (key; value.keys) { 742 static if(is(V == SysTime)) { 743 auto json = toJson(value[key]); 744 } else static if(is(V == class) || is(V == struct) || is(V == interface)) { 745 auto json = toJson!(options)(value[key]); 746 } else { 747 auto json = toJson(value[key]); 748 } 749 result[key.to!string] = json.isNull ? JSONValue(null) : json; 750 } 751 752 return result; 753 } 754 755 756 deprecated("Using toObject instead.") 757 alias fromJson = toObject; 758 } 759 760 761 alias toJson = JsonSerializer.toJson; 762 alias toObject = JsonSerializer.toObject; 763 764 765 deprecated("Using toObject instead.") 766 alias fromJson = JsonSerializer.toObject;