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 }