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 } 32 } 33 34 unittest { 35 struct Foo { 36 @JsonProperty("bar") string foo; 37 } 38 39 assert(JsonMetadata!(Foo, "foo").serialName() == "bar"); 40 } 41 42 template canZeroConstruct(T) { 43 static if (__traits(compiles, T())) { 44 enum bool canZeroConstruct = true; 45 } else { 46 enum bool canZeroConstruct = false; 47 } 48 } 49 50 unittest { 51 struct Foo { 52 int bar; 53 int andOne() { return bar + 1; } 54 } 55 56 class Bar { 57 this(int param) {} 58 } 59 assert(canZeroConstruct!Bar == false); 60 } 61 62 63 template JsonCodec(T) if(canZeroConstruct!T) { 64 import std.traits; 65 T deserialize(JSONValue json) { 66 alias TYPES = Fields!T; 67 alias NAMES = FieldNameTuple!T; 68 69 auto builder = T(); 70 foreach (i, string name ; NAMES) { 71 alias TYPE = TYPES[i]; 72 alias Codec = JsonCodec!TYPE; 73 alias META = JsonMetadata!(T, name); 74 75 TYPE value = Codec.deserialize(json[META.serialName()]); 76 __traits(getMember, builder, name) = value; 77 } 78 return builder; 79 } 80 81 void serialize(T source, JBuffer writer) { 82 writer.startObject(); 83 alias TYPES = Fields!T; 84 alias NAMES = FieldNameTuple!T; 85 86 bool leadComma = false; 87 foreach (i, string name ; NAMES) { 88 alias TYPE = TYPES[i]; 89 alias Codec = JsonCodec!TYPE; 90 alias META = JsonMetadata!(T, name); 91 92 if (leadComma) { 93 writer.comma(); 94 } 95 leadComma = true; 96 97 writer.str(META.serialName()); 98 writer.colon(); 99 Codec.serialize(__traits(getMember, source, name), writer); 100 } 101 102 writer.endObject(); 103 } 104 } 105 106 107 unittest { 108 struct Point { 109 long x = 0; 110 long y = 0; 111 } 112 113 struct Line { 114 Point from; 115 Point to; 116 string label; 117 } 118 119 auto json = parseJSON(`{"from": {"x": 0, "y": 0}, "to": {"x": 2, "y": 2}, "label": "my line"}`); 120 auto deser = JsonCodec!Line.deserialize(json); 121 assert(deser == Line(Point(0, 0), Point(2, 2), "my line")); 122 } 123 124 unittest { 125 struct User { 126 @JsonProperty("user_name") string userName; 127 } 128 129 auto json = parseJSON(`{"user_name": "Lee"}`); 130 alias codec = JsonCodec!User; 131 assert(codec.deserialize(json) == User("Lee")); 132 } 133 134 135 template JsonCodec(T : T[]) { 136 T[] deserialize(JSONValue value) { 137 alias CODEC = JsonCodec!T; 138 139 T[] ts; // TODO: pre-size this 140 foreach (i, val ; value.array()) { 141 ts ~= CODEC.deserialize(val); 142 } 143 return ts; 144 } 145 146 void serialize(T[] source, JBuffer buffer) { 147 alias CODEC = JsonCodec!T; 148 buffer.startArray(); 149 bool leadSlash = false; 150 foreach (i, s ; source) { 151 if (leadSlash) { 152 buffer.comma(); 153 } 154 leadSlash = true; 155 CODEC.serialize(s, buffer); 156 } 157 buffer.endArray(); 158 } 159 } 160 161 unittest { 162 auto json = `[1,2,3,4]`; 163 long[] longs = json.decodeJson!(long[]); 164 assert(longs == [1,2,3,4]); 165 auto encoded = longs.encodeJson; 166 assert(json == encoded); 167 } 168 169 template JsonCodec(T: long) { 170 long deserialize(JSONValue value) { 171 return value.integer(); 172 } 173 174 void serialize(long source, JBuffer buffer) { 175 buffer.numeric(source); 176 } 177 } 178 179 180 template JsonCodec(T: string) { 181 string deserialize(JSONValue value) { 182 return value.str(); 183 } 184 185 void serialize(string source, JBuffer buffer) { 186 buffer.str(source); 187 } 188 } 189 190 template JsonCodec(T: bool) { 191 bool deserialize(JSONValue value) { 192 switch(value.type()) { 193 case JSON_TYPE.TRUE: 194 return true; 195 case JSON_TYPE.FALSE: 196 return false; 197 default: 198 throw new Error("value is not a boolean"); 199 } 200 } 201 202 void serialize(bool source, JBuffer buffer) { 203 buffer.boolean(source); 204 } 205 } 206 207 private struct JBuffer { 208 private OutBuffer buffer; 209 this(OutBuffer buffer) { this.buffer = buffer; } 210 211 JBuffer startObject() { buffer.write("{"); return this; } 212 JBuffer endObject() { buffer.write("}"); return this; } 213 JBuffer startArray() { buffer.write("["); return this; } 214 JBuffer endArray() { buffer.write("]"); return this; } 215 JBuffer colon() { buffer.write(":"); return this; } 216 JBuffer comma() { buffer.write(","); return this; } 217 JBuffer numeric(long l) { buffer.writef("%d", l); return this; } 218 JBuffer str(string st) { buffer.writef(`"%s"`, escape(st)); return this; } 219 JBuffer boolean(bool b) { b ? buffer.write("true") : buffer.write("false"); return this; } 220 221 private string escape(string str) { 222 // TODO(lavital): really escape 223 return str; 224 } 225 } 226 227 T decodeJson(T)(string json) { 228 alias CODEC = JsonCodec!T; 229 JSONValue value = parseJSON(json); 230 return CODEC.deserialize(value); 231 } 232 233 string encodeJson(T)(T source) { 234 alias Codec = JsonCodec!T; 235 auto buffer = JBuffer(new OutBuffer()); 236 Codec.serialize(source, buffer); 237 return buffer.buffer.toString(); 238 } 239 240 unittest { 241 string json = `1234`; 242 auto deser = decodeJson!(long)(json); 243 assert(deser == 1234); 244 string serialized = encodeJson(deser); 245 assert(serialized == json); 246 247 json = `"hello"`; 248 string deserString = `"hello"`.decodeJson!string; 249 assert(deserString == "hello"); 250 serialized = encodeJson(deserString); 251 assert(json == serialized); 252 253 json = `true`; 254 auto deserBool = json.decodeJson!bool; 255 assert(deserBool == true); 256 serialized = encodeJson(deserBool); 257 assert(serialized == json); 258 259 struct OneField { 260 @JsonProperty("foo") string bar; 261 } 262 json = `{"foo":"hello"}`; 263 auto deserOneField = json.decodeJson!OneField; 264 assert(deserOneField == OneField("hello")); 265 serialized = encodeJson(deserOneField); 266 assert(serialized == json); 267 }