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 TYPE value = Codec.deserialize(json[META.serialName()]); 77 __traits(getMember, builder, name) = value; 78 } 79 return builder; 80 } 81 82 void serialize(T source, JBuffer writer) { 83 writer.startObject(); 84 alias TYPES = Fields!T; 85 alias NAMES = FieldNameTuple!T; 86 87 bool leadComma = false; 88 foreach (i, string name ; NAMES) { 89 alias TYPE = TYPES[i]; 90 alias Codec = JsonCodec!TYPE; 91 alias META = JsonMetadata!(T, name); 92 93 if (leadComma) { 94 writer.comma(); 95 } 96 leadComma = true; 97 98 writer.str(META.serialName()); 99 writer.colon(); 100 Codec.serialize(__traits(getMember, source, name), writer); 101 } 102 103 writer.endObject(); 104 } 105 } 106 107 108 unittest { 109 struct Point { 110 long x = 0; 111 long y = 0; 112 } 113 114 struct Line { 115 Point from; 116 Point to; 117 string label; 118 } 119 120 auto json = parseJSON(`{"from": {"x": 0, "y": 0}, "to": {"x": 2, "y": 2}, "label": "my line"}`); 121 auto deser = JsonCodec!Line.deserialize(json); 122 assert(deser == Line(Point(0, 0), Point(2, 2), "my line")); 123 } 124 125 unittest { 126 struct User { 127 @JsonProperty("user_name") string userName; 128 } 129 130 auto json = parseJSON(`{"user_name": "Lee"}`); 131 alias codec = JsonCodec!User; 132 assert(codec.deserialize(json) == User("Lee")); 133 } 134 135 136 template JsonCodec(T : T[]) { 137 T[] deserialize(JSONValue value) { 138 alias CODEC = JsonCodec!T; 139 140 T[] ts; // TODO: pre-size this 141 foreach (i, val ; value.array()) { 142 ts ~= CODEC.deserialize(val); 143 } 144 return ts; 145 } 146 147 void serialize(T[] source, JBuffer buffer) { 148 alias CODEC = JsonCodec!T; 149 buffer.startArray(); 150 bool leadSlash = false; 151 foreach (i, s ; source) { 152 if (leadSlash) { 153 buffer.comma(); 154 } 155 leadSlash = true; 156 CODEC.serialize(s, buffer); 157 } 158 buffer.endArray(); 159 } 160 } 161 162 unittest { 163 auto json = `[1,2,3,4]`; 164 long[] longs = json.decodeJson!(long[]); 165 assert(longs == [1,2,3,4]); 166 auto encoded = longs.encodeJson; 167 assert(json == encoded); 168 } 169 170 template JsonCodec(T: JSONValue) { 171 JSONValue deserialize(JSONValue v) { 172 return v; 173 } 174 175 void serialize(JSONValue value, JBuffer buffer) { 176 buffer.json(value); 177 } 178 } 179 180 unittest { 181 struct User { 182 JSONValue data; 183 } 184 string json = `{"data": {"foo": 1, "bar": [1,2,3]}}`; 185 auto decoded = json.decodeJson!User; 186 assert(decoded == User(JSONValue(["foo": JSONValue(1), "bar": JSONValue([1,2,3])]))); 187 188 assert(decoded == decoded.encodeJson().decodeJson!User); 189 } 190 191 template JsonCodec(T: long) { 192 long deserialize(JSONValue value) { 193 return value.integer(); 194 } 195 196 void serialize(long source, JBuffer buffer) { 197 buffer.numeric(source); 198 } 199 } 200 201 202 template JsonCodec(T: string) { 203 string deserialize(JSONValue value) { 204 return value.str(); 205 } 206 207 void serialize(string source, JBuffer buffer) { 208 buffer.str(source); 209 } 210 } 211 212 template JsonCodec(T: bool) { 213 bool deserialize(JSONValue value) { 214 switch(value.type()) { 215 case JSON_TYPE.TRUE: 216 return true; 217 case JSON_TYPE.FALSE: 218 return false; 219 default: 220 throw new Error("value is not a boolean"); 221 } 222 } 223 224 void serialize(bool source, JBuffer buffer) { 225 buffer.boolean(source); 226 } 227 } 228 229 private struct JBuffer { 230 private OutBuffer buffer; 231 this(OutBuffer buffer) { this.buffer = buffer; } 232 233 JBuffer startObject() { buffer.write("{"); return this; } 234 JBuffer endObject() { buffer.write("}"); return this; } 235 JBuffer startArray() { buffer.write("["); return this; } 236 JBuffer endArray() { buffer.write("]"); return this; } 237 JBuffer colon() { buffer.write(":"); return this; } 238 JBuffer comma() { buffer.write(","); return this; } 239 JBuffer numeric(long l) { buffer.writef("%d", l); return this; } 240 JBuffer str(string st) { buffer.writef(`"%s"`, escape(st)); return this; } 241 JBuffer boolean(bool b) { b ? buffer.write("true") : buffer.write("false"); return this; } 242 JBuffer json(JSONValue value) { buffer.write(value.toString()); return this; } 243 244 private string escape(string str) { 245 // TODO(lavital): really escape 246 return str; 247 } 248 } 249 250 251 /** 252 * Decode a JSON string into some datatype. 253 */ 254 T decodeJson(T)(string json) { 255 alias CODEC = JsonCodec!T; 256 JSONValue value = parseJSON(json); 257 return CODEC.deserialize(value); 258 } 259 260 /// 261 unittest { 262 struct User { 263 @JsonProperty("user_name") string userName; 264 } 265 266 string json = `{"user_name": "John Smith"}`; 267 auto decoded = json.decodeJson!User; 268 assert(decoded == User("John Smith")); 269 270 } 271 272 273 /** 274 * Encode an object into a JSON string. 275 */ 276 string encodeJson(T)(T source) { 277 alias Codec = JsonCodec!T; 278 auto buffer = JBuffer(new OutBuffer()); 279 Codec.serialize(source, buffer); 280 return buffer.buffer.toString(); 281 } 282 283 /// 284 unittest { 285 string json = `1234`; 286 auto deser = decodeJson!(long)(json); 287 assert(deser == 1234); 288 string serialized = encodeJson(deser); 289 assert(serialized == json); 290 291 json = `"hello"`; 292 string deserString = `"hello"`.decodeJson!string; 293 assert(deserString == "hello"); 294 serialized = encodeJson(deserString); 295 assert(json == serialized); 296 297 json = `true`; 298 auto deserBool = json.decodeJson!bool; 299 assert(deserBool == true); 300 serialized = encodeJson(deserBool); 301 assert(serialized == json); 302 303 struct OneField { 304 @JsonProperty("foo") string bar; 305 } 306 json = `{"foo":"hello"}`; 307 auto deserOneField = json.decodeJson!OneField; 308 assert(deserOneField == OneField("hello")); 309 serialized = encodeJson(deserOneField); 310 assert(serialized == json); 311 }