1 module dackson; 2 3 import std.stdio; 4 import std.json; 5 import std.outbuffer; 6 7 // annotations 8 struct JsonProperty { 9 string name; 10 alias name this; 11 } 12 13 // retrieve metadata about how json serde should work for a field in a given type 14 template JsonMetadata(T, string field) { 15 import std.traits; 16 alias names = FieldNameTuple!T; 17 alias TYPES = Fields!T; 18 19 // the name of the 20 string serialName() { 21 foreach (name ; names) { 22 if (name == field) { 23 auto udas = getUDAs!(__traits(getMember, T, name), JsonProperty); 24 string ret = field; 25 static if (udas.length != 0) { 26 ret = udas[0]; 27 } 28 return ret; 29 } 30 } 31 assert(0); 32 } 33 } 34 35 unittest { 36 struct Foo { 37 @JsonProperty("bar") string foo; 38 } 39 40 assert(JsonMetadata!(Foo, "foo").serialName() == "bar"); 41 } 42 43 template canZeroConstruct(T) { 44 static if (__traits(compiles, T())) { 45 enum bool canZeroConstruct = true; 46 } else { 47 enum bool canZeroConstruct = false; 48 } 49 } 50 51 unittest { 52 struct Foo { 53 int bar; 54 int andOne() { return bar + 1; } 55 } 56 57 class Bar { 58 this(int param) {} 59 } 60 assert(canZeroConstruct!Bar == false); 61 } 62 63 64 template JsonCodec(T) if(canZeroConstruct!T) { 65 import std.traits; 66 T deserialize(JSONValue json) { 67 alias TYPES = Fields!T; 68 alias NAMES = FieldNameTuple!T; 69 70 auto builder = T(); 71 foreach (i, string name ; NAMES) { 72 alias TYPE = TYPES[i]; 73 alias Codec = JsonCodec!TYPE; 74 alias META = JsonMetadata!(T, name); 75 76 if (META.serialName() in json) { 77 TYPE value = Codec.deserialize(json[META.serialName()]); 78 __traits(getMember, builder, name) = value; 79 } 80 } 81 return builder; 82 } 83 84 void serialize(T source, JBuffer writer) { 85 writer.startObject(); 86 alias TYPES = Fields!T; 87 alias NAMES = FieldNameTuple!T; 88 89 bool leadComma = false; 90 foreach (i, string name ; NAMES) { 91 alias TYPE = TYPES[i]; 92 alias Codec = JsonCodec!TYPE; 93 alias META = JsonMetadata!(T, name); 94 95 if (leadComma) { 96 writer.comma(); 97 } 98 leadComma = true; 99 100 writer.str(META.serialName()); 101 writer.colon(); 102 Codec.serialize(__traits(getMember, source, name), writer); 103 } 104 105 writer.endObject(); 106 } 107 } 108 109 110 unittest { 111 struct Point { 112 long x = 0; 113 long y = 0; 114 } 115 116 struct Line { 117 Point from; 118 Point to; 119 string label; 120 } 121 122 auto json = parseJSON(`{"from": {"x": 0, "y": 0}, "to": {"x": 2, "y": 2}, "label": "my line"}`); 123 auto deser = JsonCodec!Line.deserialize(json); 124 assert(deser == Line(Point(0, 0), Point(2, 2), "my line")); 125 } 126 127 128 unittest { 129 struct User { 130 @JsonProperty("user_name") string userName; 131 } 132 133 auto json = parseJSON(`{"user_name": "Lee"}`); 134 alias codec = JsonCodec!User; 135 assert(codec.deserialize(json) == User("Lee")); 136 } 137 138 139 template JsonCodec(T : T[]) { 140 T[] deserialize(JSONValue value) { 141 alias CODEC = JsonCodec!T; 142 143 T[] ts; // TODO: pre-size this 144 foreach (i, val ; value.array()) { 145 ts ~= CODEC.deserialize(val); 146 } 147 return ts; 148 } 149 150 void serialize(T[] source, JBuffer buffer) { 151 alias CODEC = JsonCodec!T; 152 buffer.startArray(); 153 bool leadSlash = false; 154 foreach (i, s ; source) { 155 if (leadSlash) { 156 buffer.comma(); 157 } 158 leadSlash = true; 159 CODEC.serialize(s, buffer); 160 } 161 buffer.endArray(); 162 } 163 } 164 165 unittest { 166 auto json = `[1,2,3,4]`; 167 long[] longs = json.decodeJson!(long[]); 168 assert(longs == [1,2,3,4]); 169 auto encoded = longs.encodeJson; 170 assert(json == encoded); 171 } 172 173 template JsonCodec(T: JSONValue) { 174 JSONValue deserialize(JSONValue v) { 175 return v; 176 } 177 178 void serialize(JSONValue value, JBuffer buffer) { 179 buffer.json(value); 180 } 181 } 182 183 unittest { 184 struct User { 185 JSONValue data; 186 } 187 string json = `{"data": {"foo": 1, "bar": [1,2,3]}}`; 188 auto decoded = json.decodeJson!User; 189 assert(decoded == User(JSONValue(["foo": JSONValue(1), "bar": JSONValue([1,2,3])]))); 190 191 assert(decoded == decoded.encodeJson().decodeJson!User); 192 } 193 194 template JsonCodec(T: long) { 195 long deserialize(JSONValue value) { 196 return value.integer(); 197 } 198 199 void serialize(long source, JBuffer buffer) { 200 buffer.numeric(source); 201 } 202 } 203 204 205 template JsonCodec(T: string) { 206 string deserialize(JSONValue value) { 207 return value.str(); 208 } 209 210 void serialize(string source, JBuffer buffer) { 211 buffer.str(source); 212 } 213 } 214 215 template JsonCodec(T: bool) { 216 bool deserialize(JSONValue value) { 217 switch(value.type()) { 218 case JSON_TYPE.TRUE: 219 return true; 220 case JSON_TYPE.FALSE: 221 return false; 222 default: 223 throw new Error("value is not a boolean"); 224 } 225 } 226 227 void serialize(bool source, JBuffer buffer) { 228 buffer.boolean(source); 229 } 230 } 231 232 private struct JBuffer { 233 private OutBuffer buffer; 234 this(OutBuffer buffer) { this.buffer = buffer; } 235 236 JBuffer startObject() { buffer.write("{"); return this; } 237 JBuffer endObject() { buffer.write("}"); return this; } 238 JBuffer startArray() { buffer.write("["); return this; } 239 JBuffer endArray() { buffer.write("]"); return this; } 240 JBuffer colon() { buffer.write(":"); return this; } 241 JBuffer comma() { buffer.write(","); return this; } 242 JBuffer numeric(long l) { buffer.writef("%d", l); return this; } 243 JBuffer str(string st) { buffer.writef(`"%s"`, escape(st)); return this; } 244 JBuffer boolean(bool b) { b ? buffer.write("true") : buffer.write("false"); return this; } 245 JBuffer json(JSONValue value) { buffer.write(value.toString()); return this; } 246 247 private string escape(string str) { 248 // TODO(lavital): really escape 249 return str; 250 } 251 } 252 253 254 /** 255 * Decode a JSON string into some datatype. 256 */ 257 T decodeJson(T)(string json) { 258 alias CODEC = JsonCodec!T; 259 JSONValue value = parseJSON(json); 260 return CODEC.deserialize(value); 261 } 262 263 /// 264 unittest { 265 struct User { 266 @JsonProperty("user_name") string userName; 267 } 268 269 string json = `{"user_name": "John Smith"}`; 270 auto decoded = json.decodeJson!User; 271 assert(decoded == User("John Smith")); 272 273 } 274 275 276 /** 277 * Encode an object into a JSON string. 278 */ 279 string encodeJson(T)(T source) { 280 alias Codec = JsonCodec!T; 281 auto buffer = JBuffer(new OutBuffer()); 282 Codec.serialize(source, buffer); 283 return buffer.buffer.toString(); 284 } 285 286 /// 287 unittest { 288 string json = `1234`; 289 auto deser = decodeJson!(long)(json); 290 assert(deser == 1234); 291 string serialized = encodeJson(deser); 292 assert(serialized == json); 293 294 json = `"hello"`; 295 string deserString = `"hello"`.decodeJson!string; 296 assert(deserString == "hello"); 297 serialized = encodeJson(deserString); 298 assert(json == serialized); 299 300 json = `true`; 301 auto deserBool = json.decodeJson!bool; 302 assert(deserBool == true); 303 serialized = encodeJson(deserBool); 304 assert(serialized == json); 305 306 struct OneField { 307 @JsonProperty("foo") string bar; 308 } 309 json = `{"foo":"hello"}`; 310 auto deserOneField = json.decodeJson!OneField; 311 assert(deserOneField == OneField("hello")); 312 serialized = encodeJson(deserOneField); 313 assert(serialized == json); 314 315 json = `{}`; 316 auto emptyOneField = json.decodeJson!OneField; 317 assert(emptyOneField == OneField(null)); 318 }