1 module jsonizer.internal.attribute; 2 3 /// use @jsonize to mark members to be (de)serialized from/to json 4 /// use @jsonize to mark a single contructor to use when creating an object using extract 5 /// use @jsonize("name") to make a member use the json key "name" 6 /// use @jsonize(JsonizeOptional.[yes/no]) to choose whether the parameter is optional 7 struct jsonize { 8 /// alternate name used to identify member in json 9 string key; 10 /// whether member is required during deserialization 11 JsonizeOptional optional = JsonizeOptional.unspecified; 12 13 /// parameters to @jsonize may be specified in any order 14 /// valid uses of @jsonize include: 15 /// @jsonize 16 /// @jsonize("foo") 17 /// @jsonize(JsonizeOptional.yes) 18 /// @jsonize("bar", JsonizeOptional.yes) 19 /// @jsonize(JsonizeOptional.yes, "bar") 20 this(T ...)(T params) { 21 foreach(idx , param ; params) { 22 alias type = T[idx]; 23 static if (is(type == JsonizeOptional)) { 24 optional = param; 25 } 26 else static if (is(type : string)) { 27 key = param; 28 } 29 else { 30 assert(0, "invalid @jsonize parameter of type " ~ typeid(type)); 31 } 32 } 33 } 34 } 35 36 /// whether to fail deserialization if field is not found in json 37 enum JsonizeOptional { 38 unspecified, /// optional status not specified (currently defaults to `no`) 39 no, /// field is required -- fail deserialization if not found in json 40 yes /// field is optional -- deserialization can continue if field is not found in json 41 } 42 43 /// Use of `JsonizeOptional`: 44 unittest { 45 import std.json : parseJSON; 46 import std.exception : collectException, assertNotThrown; 47 import jsonizer.jsonize : JsonizeMe; 48 import jsonizer.fromjson : fromJSON; 49 import jsonizer.exceptions : JsonizeMismatchException; 50 static struct S { 51 mixin JsonizeMe; 52 53 @jsonize { 54 int i; // i is non-optional (default) 55 @jsonize(JsonizeOptional.yes) { 56 @jsonize("_s") string s; // s is optional 57 @jsonize(JsonizeOptional.no) float f; // f is non-optional (overrides outer attribute) 58 } 59 } 60 } 61 62 assertNotThrown(`{ "i": 5, "f": 0.2}`.parseJSON.fromJSON!S); 63 auto ex = collectException!JsonizeMismatchException(`{ "i": 5 }`.parseJSON.fromJSON!S); 64 65 assert(ex !is null, "missing non-optional field 'f' should trigger JsonizeMismatchException"); 66 assert(ex.targetType == typeid(S)); 67 assert(ex.missingKeys == [ "f" ]); 68 assert(ex.extraKeys == [ ]); 69 } 70 71 // TODO: use std.typecons : Flag instead? Would likely need to public import. 72 /// Whether to silently ignore json keys that do not map to serialized members. 73 enum JsonizeIgnoreExtraKeys { 74 no, /// silently ignore extra keys in the json object being deserialized 75 yes /// fail if the json object contains a keys that does not map to a serialized field 76 } 77 78 79 /// Use of `JsonizeIgnoreExtraKeys`: 80 unittest { 81 import std.json : parseJSON; 82 import std.exception : collectException, assertNotThrown; 83 import jsonizer.jsonize : JsonizeMe; 84 import jsonizer.fromjson : fromJSON; 85 import jsonizer.exceptions : JsonizeMismatchException; 86 87 static struct NoCares { 88 mixin JsonizeMe; 89 @jsonize { 90 int i; 91 float f; 92 } 93 } 94 95 static struct VeryStrict { 96 mixin JsonizeMe!(JsonizeIgnoreExtraKeys.no); 97 @jsonize { 98 int i; 99 float f; 100 } 101 } 102 103 // no extra fields, neither should throw 104 assertNotThrown(`{ "i": 5, "f": 0.2}`.parseJSON.fromJSON!NoCares); 105 assertNotThrown(`{ "i": 5, "f": 0.2}`.parseJSON.fromJSON!VeryStrict); 106 107 // extra field "s" 108 // `NoCares` ignores extra keys, so it will not throw 109 assertNotThrown(`{ "i": 5, "f": 0.2, "s": "hi"}`.parseJSON.fromJSON!NoCares); 110 // `VeryStrict` does not ignore extra keys 111 auto ex = collectException!JsonizeMismatchException( 112 `{ "i": 5, "f": 0.2, "s": "hi"}`.parseJSON.fromJSON!VeryStrict); 113 114 assert(ex !is null, "extra field 's' should trigger JsonizeMismatchException"); 115 assert(ex.targetType == typeid(VeryStrict)); 116 assert(ex.missingKeys == [ ]); 117 assert(ex.extraKeys == [ "s" ]); 118 }