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 }