1 // Written in the D programming language.
2
3 /**
4 * Yajl Encoder
5 *
6 * Example:
7 * -----
8 * Encoder encoder;
9 * assert(encoder.encode(["foo":"bar"] == `{"foo":"bar"}`));
10 * -----
11 *
12 * with option:
13 * -----
14 * Encoder.Option opt;
15 * opt.beautify = true;
16 * opt.indentString = " ";
17 * Encoder encoder = Encoder(opt);
18 * assert(encoder.encode(["foo":"bar"] ==
19 * `{
20 * "foo": "bar"
21 * }
22 * `));
23 * -----
24 *
25 * See_Also:
26 * $(LINK2 http://lloyd.github.com/yajl/yajl-2.0.1/yajl__gen_8h.html, Yajl gen header)$(BR)
27 *
28 * Copyright: Copyright Masahiro Nakagawa 2013-.
29 * License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
30 * Authors: Masahiro Nakagawa
31 */
32 module yajl.encoder;
33
34 import yajl.c.gen;
35 import yajl.common;
36
37 import std.traits;
38
39
40 /**
41 * Encoder provides the method for serializing a D object into JSON format.
42 */
43 struct Encoder
44 {
45 //alias yajl_print_t PrintCallback;
46
47 /// See: http://lloyd.github.io/yajl/yajl-2.1.0/yajl__gen_8h.html#a57c29080044a7231ac0cf1fead4de4b0
48 static struct Option
49 {
50 bool beautify;
51 bool validateUTF8;
52 bool escapeSolidus;
53 string indentString;
54 //PrintCallback printCallback; // TODO: Fix "killed by signal 11"
55 }
56
57 private:
58 yajl_gen _gen;
59
60 public:
61 /**
62 * Constructs an Encoder object with $(D_PARAM opt).
63 */
64 @trusted
65 this(ref Option opt)
66 {
67 initialize();
68 setEncoderConfig(_gen, opt);
69 }
70
71 @trusted
72 ~this()
73 {
74 clear();
75 }
76
77 /**
78 * Encodes an argument and returns the JSON.
79 */
80 @trusted
81 string encode(T)(auto ref T value)
82 {
83 initialize();
84
85 yajlGenerate(_gen, value);
86
87 ubyte* resultBuffer;
88 size_t resultLength;
89
90 yajl_gen_get_buf(_gen, &resultBuffer, &resultLength);
91
92 string result = cast(string)resultBuffer[0..resultLength].dup;
93
94 // reset state and internal buffer for next encode
95 yajl_gen_reset(_gen, null);
96 yajl_gen_clear(_gen);
97
98 return result;
99 }
100
101 private:
102 void initialize()
103 {
104 if (_gen is null)
105 _gen = yajl_gen_alloc(&yajlAllocFuncs);
106 }
107
108 void clear()
109 {
110 if (_gen !is null) {
111 yajl_gen_free(_gen);
112 _gen = null;
113 }
114 }
115
116 @trusted
117 static void setEncoderConfig(yajl_gen gen, ref Encoder.Option opt)
118 {
119 import std.string : toStringz;
120
121 if (opt.beautify)
122 yajl_gen_config(gen, yajl_gen_option.yajl_gen_beautify, 1);
123 if (opt.validateUTF8)
124 yajl_gen_config(gen, yajl_gen_option.yajl_gen_validate_utf8, 1);
125 if (opt.escapeSolidus)
126 yajl_gen_config(gen, yajl_gen_option.yajl_gen_escape_solidus, 1);
127 if (opt.indentString)
128 yajl_gen_config(gen, yajl_gen_option.yajl_gen_indent_string, toStringz(opt.indentString));
129 //if (opt.printCallback)
130 // yajl_gen_config(gen, yajl_gen_option.yajl_gen_print_callback, opt.printCallback);
131 }
132 }
133
134 unittest
135 {
136 static struct Handa
137 {
138 ulong id;
139 string name;
140 double height;
141 }
142
143 Handa handa = Handa(1000, "shinobu", 169.5);
144
145 {
146 Encoder encoder;
147 assert(encoder.encode(handa) == `{"id":1000,"name":"shinobu","height":169.5}`);
148 }
149 { // opt
150 Encoder.Option opt;
151 opt.beautify = true;
152 opt.indentString = " ";
153 assert(Encoder(opt).encode(handa) == `{
154 "id": 1000,
155 "name": "shinobu",
156 "height": 169.5
157 }
158 `);
159 }
160 }
161
162 unittest
163 {
164 static struct JSONNameTest
165 {
166 @JSONName("body") string _body;
167 }
168
169 JSONNameTest jnt = JSONNameTest("body!");
170
171 {
172 Encoder encoder;
173 assert(encoder.encode(jnt) == `{"body":"body!"}`);
174 }
175 }
176
177 private:
178
179 @trusted
180 void yajlGenerate(T)(yajl_gen gen, auto ref T value)
181 {
182 import std.conv : to;
183 import std.typecons : isTuple;
184
185 static if (isBoolean!T)
186 {
187 checkStatus(yajl_gen_bool(gen, value ? 1 : 0));
188 }
189 else static if (isIntegral!T)
190 {
191 checkStatus(yajl_gen_integer(gen, value));
192 }
193 else static if (isFloatingPoint!T)
194 {
195 checkStatus(yajl_gen_double(gen, value));
196 }
197 else static if (isSomeString!T)
198 {
199 checkStatus(yajl_gen_string(gen, cast(const(ubyte)*)value.ptr, value.length));
200 }
201 else static if (isArray!T)
202 {
203 if (value is null) {
204 checkStatus(yajl_gen_null(gen));
205 } else {
206 checkStatus(yajl_gen_array_open(gen));
207 foreach (ref v; value)
208 yajlGenerate(gen, v);
209 checkStatus(yajl_gen_array_close(gen));
210 }
211 }
212 else static if (isAssociativeArray!T)
213 {
214 if (value is null) {
215 checkStatus(yajl_gen_null(gen));
216 } else {
217 checkStatus(yajl_gen_map_open(gen));
218 foreach (k, ref v; value) {
219 yajlGenerate(gen, to!string(k));
220 yajlGenerate(gen, v);
221 }
222 checkStatus(yajl_gen_map_close(gen));
223 }
224 }
225 else static if (isTuple!T)
226 {
227 checkStatus(yajl_gen_array_open(gen));
228 foreach (i, Type; T.Types)
229 yajlGenerate(gen, value[i]);
230 checkStatus(yajl_gen_array_close(gen));
231 }
232 else static if (is(T == struct) || is(T == class))
233 {
234 static if (is(T == class))
235 {
236 if (value is null) {
237 checkStatus(yajl_gen_null(gen));
238 return;
239 }
240 }
241
242 checkStatus(yajl_gen_map_open(gen));
243 foreach (i, ref v; value.tupleof) {
244 static if (isNullable!(typeof(v)))
245 {
246 if (!v.isNull) {
247 yajlGenerate(gen, getFieldName!(T, i));
248 yajlGenerate(gen, v.get);
249 }
250 }
251 else
252 {
253 yajlGenerate(gen, getFieldName!(T, i));
254 yajlGenerate(gen, v);
255 }
256 }
257 checkStatus(yajl_gen_map_close(gen));
258 }
259
260 return;
261 }
262
263 // status check is needed?
264 @safe
265 void checkStatus(yajl_gen_status status) pure
266 {
267 if (status != yajl_gen_status.yajl_gen_status_ok)
268 throw new YajlException(formatStatus(status));
269 }
270
271 @safe
272 string formatStatus(in yajl_gen_status status) pure
273 {
274 final switch (status) {
275 case yajl_gen_status.yajl_gen_status_ok:
276 return null;
277 case yajl_gen_status.yajl_gen_keys_must_be_strings:
278 return "A map key is generated, a function other than yajl_gen_string was called";
279 case yajl_gen_status.yajl_max_depth_exceeded:
280 return "YAJL's maximum generation depth was exceeded";
281 case yajl_gen_status.yajl_gen_in_error_state:
282 return "A generator function was called while in an error state";
283 case yajl_gen_status.yajl_gen_generation_complete:
284 return "A complete JSON document has been generated";
285 case yajl_gen_status.yajl_gen_invalid_number:
286 return "Invalid floating point value (infinity or NaN)";
287 case yajl_gen_status.yajl_gen_no_buf:
288 return "There is no internal buffer to get from print callback";
289 case yajl_gen_status.yajl_gen_invalid_string:
290 return "Returned invalid utf8 string from yajl_gen_string";
291 }
292 }