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 }