1 // Written in the D programming language.
2 
3 /**
4  * Yajl Decoder
5  *
6  * Example:
7  * -----
8  * Decoder decoder;
9  * if (decoder.decode(`{"foo":"bar"}`))
10  *     assert(decoder.decodedValue!(string[string]) == ["foo":"bar"]);
11  * -----
12  *
13  * See_Also:
14  *  $(LINK2 http://lloyd.github.com/yajl/yajl-2.0.1/yajl__parse_8h.html, Yajl parse header)$(BR)
15  *
16  * Copyright: Copyright Masahiro Nakagawa 2013-.
17  * License:   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
18  * Authors:   Masahiro Nakagawa
19  */
20 module yajl.decoder;
21 
22 import yajl.c.parse;
23 import yajl.common;
24 
25 public import std.json;
26 import std.array : popBack;
27 import std.conv;
28 
29 
30 /**
31  * Decoder provides the method for deserializing JSON into a D object.
32  */
33 struct Decoder
34 {
35     alias void delegate(string) MissingHandler;
36 
37     /// See: http://lloyd.github.io/yajl/yajl-2.1.0/yajl__parse_8h.html#a5434a7c3b3165d782ea42c17d6ba9ac3
38     static struct Option
39     {
40         bool allowComments;
41         bool dontValidateStrings;
42         bool allowTrailingGarbage;
43         bool allowMultipleValues;
44         bool allowPartialValue;
45 
46         MissingHandler missingHandler;
47     }
48 
49   private:
50     static enum ContainerType
51     {
52         arrayItem,
53         mapKey,
54         mapValue
55     }
56 
57     static struct Container
58     {
59         ContainerType type; // value container type
60         JSONValue value;    // current value
61         string key;         // for map value
62     }
63 
64     yajl_handle _handle;
65     Container[] _stack;
66     size_t _nested;
67     MissingHandler _missingHandler;
68 
69   public:
70     /**
71      * Constructs an Decoder object with $(D_PARAM opt).
72      */
73     @trusted
74     this(ref Option opt)
75     {
76         initialize();
77         setDecoderConfig(opt);
78     }
79 
80     @trusted
81     ~this()
82     {
83         clear();
84     }
85 
86     @property
87     {
88         /**
89          * Returns the decoded object.
90          */
91         nothrow ref inout(T) decodedValue(T = JSONValue)() inout if (is(T : JSONValue))
92         {
93             return _stack[0].value;
94         }
95 
96         /// ditto
97         inout(T) decodedValue(T = JSONValue)() inout if (!is(T : JSONValue))
98         {
99             return cast(inout(T))fromJSONValue!T(_stack[0].value, this);
100         }
101     }
102 
103     /**
104      * Try to decode the $(D_PARAM json). The decoded result is retrieved from $(LREF decodedValue).
105      *
106      * Returns:
107      *  true if parsing succeeded. Passed json is insufficient, returns false.
108      *
109      * Throws:
110      *  a YajlException when parsing error ocurred.
111      */
112     bool decode(in const(char)[] json)
113     {
114         initialize();
115 
116         checkStatus(yajl_parse(_handle, cast(const(ubyte)*)json.ptr, json.length), json);
117         if (_nested == 0) {
118             checkStatus(yajl_complete_parse(_handle), json);
119             return true;
120         }
121 
122         return false;
123     }
124 
125   private:
126     void initialize()
127     {
128         if (_handle is null)
129             _handle = yajl_alloc(&yajlCallbacks, &yajlAllocFuncs, &this);
130 
131         if (_nested == 0)
132             _stack.destroy();
133     }
134 
135     void clear()
136     {
137         if (_handle !is null) {
138             yajl_free(_handle);
139             _handle = null;
140         }
141     }
142 
143     @safe
144     void checkStatus(in yajl_status status, lazy const(char)[] json)
145     {
146         if (status != yajl_status.yajl_status_ok)
147             throw new YajlException(formatStatus(_handle, json));
148     }
149 
150     @trusted
151     void setDecoderConfig(ref Decoder.Option opt)
152     {
153         if (opt.allowComments)
154             yajl_config(_handle, yajl_option.yajl_allow_comments, 1);
155         if (opt.dontValidateStrings)
156             yajl_config(_handle, yajl_option.yajl_dont_validate_strings, 1);
157         if (opt.allowTrailingGarbage)
158             yajl_config(_handle, yajl_option.yajl_allow_trailing_garbage, 1);
159         if (opt.allowMultipleValues)
160             yajl_config(_handle, yajl_option.yajl_allow_multiple_values, 1);
161         if (opt.allowPartialValue)
162             yajl_config(_handle, yajl_option.yajl_allow_partial_values, 1);
163         if (opt.missingHandler)
164             _missingHandler = opt.missingHandler;
165     }
166 }
167 
168 unittest
169 {
170     static struct Handa
171     {
172         ulong id;
173         string name;
174         double height;
175         int[] arr;
176 
177         bool opEquals(const Handa other)
178         {
179             return (id == other.id) && (name == other.name) && (height == other.height) && (arr == other.arr);
180         }
181     }
182 
183     Handa handa = Handa(1000, "shinobu", 170.0, [1, 2]);
184     immutable json = `{"id":1000,"name":"shinobu","height":170.0,"arr":[1,2]}`;
185     { // normal
186         Decoder decoder;
187         assert(decoder.decode(json));
188         assert(decoder.decodedValue!Handa == handa);
189     }
190     { // with splitted json
191         Decoder decoder;
192         assert(!decoder.decode(`{"id":1000,"name":"shino`));
193         assert(decoder.decode(`bu","height":170.0,"arr":[1,2]}`));
194         assert(decoder.decodedValue!Handa == handa);
195     }
196     { // with comments
197         Decoder.Option opt;
198         opt.allowComments = true;
199 
200         Decoder decoder = Decoder(opt);
201         assert(decoder.decode(`{/* test */ "foo":"bar"}`));
202         assert(decoder.decodedValue!(string[string]) == ["foo":"bar"]);
203     }
204     { // with multiple values
205         Decoder.Option opt;
206         opt.allowMultipleValues = true;
207 
208         int i;
209         Decoder decoder = Decoder(opt);
210         foreach (_; 0..10) {
211             assert(decoder.decode(json));
212             assert(decoder.decodedValue!Handa == handa);
213             i++;
214         }
215         assert(i == 10);
216     }
217     { // json array
218         static struct Sect
219         {
220             string attr = "sect";
221 
222             bool opEquals(const Sect other)
223             {
224                 return attr == other.attr;
225             }
226         }
227         immutable composed = `[{"attr":"sect"},{"attr":"sect"},{"attr":"sect"}]`;
228 
229         Decoder decoder;
230         assert(decoder.decode(composed));
231         assert(decoder.decodedValue!(Sect[]) == [Sect(), Sect(), Sect()]);
232     }
233     { // with missing handler
234         int count = 0;
235         Decoder.Option opt;
236         opt.missingHandler = (string fieldName) { count++; };
237 
238         Decoder decoder = Decoder(opt);
239         assert(decoder.decode(`{"id":1000,"name":"shinobu"}`));
240         assert(decoder.decodedValue!(Handa).id == 1000);
241         assert(count == 2);
242     }
243     { // parse non-json object
244         Decoder decoder;
245         try {
246             decoder.decode(`"string"`);
247             assert(false, "Why reach here?");
248         } catch (Exception e) { }
249     }
250 }
251 
252 private:
253 
254 @trusted
255 string formatStatus(yajl_handle handle, in const(char)[] json)
256 {
257     import std.c..string : strlen;
258 
259     auto msg = yajl_get_error(handle, 1, cast(const(ubyte)*)json.ptr, json.length);
260     scope(exit) { yajl_free_error(handle, msg); }
261 
262     return cast(string)(msg[0..strlen(cast(const(char*))msg)].dup);
263 }
264 
265 @trusted
266 void setParsedValue(T)(void* ctx, auto ref T value)
267 {
268     import std.conv : text;
269 
270     Decoder* decoder = cast(Decoder*)ctx;
271     JSONValue v = JSONValue(value);
272     if (decoder._nested == 0)
273         throw new YajlException(text("JSON must be object or array: type = ", v.type));
274 
275     setParsedValueToContainer(decoder, v);
276 }
277 
278 @trusted
279 void setParsedValueToContainer(Decoder* decoder, ref JSONValue value)
280 {
281     auto container = &decoder._stack[decoder._nested - 1];
282     final switch (container.type) {
283     case Decoder.ContainerType.arrayItem:
284         container.value.array ~= value;
285         break;
286     case Decoder.ContainerType.mapKey:
287         container.key = value.str;
288         container.type = Decoder.ContainerType.mapValue;
289         break;
290     case Decoder.ContainerType.mapValue:
291         container.value.object[container.key] = value;
292         container.type = Decoder.ContainerType.mapKey;
293         break;
294     }
295 }
296 
297 extern(C)
298 {
299     int callbackNull(void* ctx)
300     {
301         setParsedValue(ctx, null);
302 
303         return 1;
304     }
305 
306     int callbackBool(void* ctx, int boolean)
307     {
308         setParsedValue(ctx, boolean != 0);
309 
310         return 1;
311     }
312 
313     int callbackNumber(void* ctx, const(char)* buf, size_t len)
314     {
315         static bool checkFloatFormat(const(char)* b, size_t l)
316         {
317             import std.c..string;
318 
319             return memchr(b, '.', l) ||
320                    memchr(b, 'e', l) ||
321                    memchr(b, 'E', l);
322         }
323 
324         if (checkFloatFormat(buf, len))
325             setParsedValue(ctx, to!real(buf[0..len]));
326         else
327             setParsedValue(ctx, to!long(buf[0..len]));
328 
329         return 1;
330     }
331 
332     int callbackString(void* ctx, const(ubyte)* buf, size_t len)
333     {
334         setParsedValue(ctx, cast(string)(buf[0..len].dup));
335 
336         return 1;
337     }
338 
339     int callbackStartMap(void* ctx)
340     {
341         JSONValue[string] obj;
342         JSONValue value = JSONValue(obj);
343 
344         Decoder* decoder = cast(Decoder*)ctx;
345         decoder._nested++;
346         decoder._stack.length = decoder._nested;
347         decoder._stack[decoder._nested - 1] = Decoder.Container(Decoder.ContainerType.mapKey, value);
348 
349         return 1;
350     }
351 
352     int callbackMapKey(void* ctx, const(ubyte)* buf, size_t len)
353     {
354         setParsedValue(ctx, cast(string)(buf[0..len]));
355 
356         return 1;
357     }
358 
359     int callbackEndMap(void* ctx)
360     {
361         Decoder* decoder = cast(Decoder*)ctx;
362         decoder._nested--;
363         if (decoder._nested > 0) {
364             setParsedValueToContainer(decoder, decoder._stack[decoder._nested].value);
365             decoder._stack.popBack();
366         }
367 
368         return 1;
369     }
370 
371     int callbackStartArray(void* ctx)
372     {
373         JSONValue[] arr;
374         JSONValue value = JSONValue(arr);
375 
376         Decoder* decoder = cast(Decoder*)ctx;
377         decoder._nested++;
378         decoder._stack.length = decoder._nested;
379         decoder._stack[decoder._nested - 1] = Decoder.Container(Decoder.ContainerType.arrayItem, value);
380 
381         return 1;
382     }
383 
384     int callbackEndArray(void* ctx)
385     {
386         Decoder* decoder = cast(Decoder*)ctx;
387         decoder._nested--;
388         if (decoder._nested > 0) {
389             setParsedValueToContainer(decoder, decoder._stack[decoder._nested].value);
390             decoder._stack.popBack();
391         }
392 
393         return 1;
394     }
395 
396     yajl_callbacks yajlCallbacks = yajl_callbacks(&callbackNull,
397                                                   &callbackBool,
398                                                   null,
399                                                   null,
400                                                   &callbackNumber,
401                                                   &callbackString,
402                                                   &callbackStartMap,
403                                                   &callbackMapKey,
404                                                   &callbackEndMap,
405                                                   &callbackStartArray,
406                                                   &callbackEndArray);
407 }
408 
409 @trusted
410 T fromJSONValue(T)(ref const JSONValue value, ref const Decoder decoder)
411 {
412     import std.array : array;
413     import std.algorithm : map;
414     import std.range : ElementType;
415     import std.traits;
416 
417     @trusted
418     void typeMismatch(string type)
419     {
420         throw new JSONException(text("Not ", type,": type = ", value.type));
421     }
422 
423     T result;
424 
425     static if (is(Unqual!T U: Nullable!U))
426     {
427         result = fromJSONValue!U(value, decoder);
428     }
429     else static if (is(Unqual!T == enum))
430     {
431         auto temp = fromJSONValue!(OriginalType!T)(value, decoder);
432         result = cast(T)(temp);
433     }
434     else static if (isBoolean!T)
435     {
436         if (value.type != JSON_TYPE.TRUE && value.type != JSON_TYPE.FALSE)
437             typeMismatch("boolean");
438         result = value.type == JSON_TYPE.TRUE;
439     }
440     else static if (isIntegral!T)
441     {
442         if (value.type != JSON_TYPE.INTEGER)
443             typeMismatch("integer");
444         result = value.integer.to!T();
445     }
446     else static if (isFloatingPoint!T)
447     {
448         switch (value.type) {
449         case JSON_TYPE.FLOAT:
450             result = value.floating.to!T();
451             break;
452         case JSON_TYPE.INTEGER:
453             result = value.integer.to!T();
454             break;
455         case JSON_TYPE.STRING: // for "INF"
456             result = value.str.to!T();
457             break;
458         default:
459             typeMismatch("floating point");
460         }
461     }
462     else static if (isSomeString!T)
463     {
464         if (value.type == JSON_TYPE.NULL)
465             return null;
466         if (value.type != JSON_TYPE.STRING)
467             typeMismatch("string");
468         result = value.str.to!T();
469     }
470     else static if (isArray!T)
471     {
472         if (value.type == JSON_TYPE.NULL)
473             return null;
474         if (value.type != JSON_TYPE.ARRAY)
475             typeMismatch("array");
476         result = array(map!((a){ return fromJSONValue!(ElementType!T)(a, decoder); })(value.array));
477     }
478     else static if (isAssociativeArray!T)
479     {
480         if (value.type == JSON_TYPE.NULL)
481             return null;
482         if (value.type != JSON_TYPE.OBJECT)
483             typeMismatch("object");
484         foreach (k, v; value.object)
485             result[k] = fromJSONValue!(ValueType!T)(v, decoder);
486     }
487     else static if (is(T == struct) || is(T == class))
488     {
489         static if (is(T == class))
490         {
491             if (value.type == JSON_TYPE.NULL)
492                 return null;
493         }
494 
495         if (value.type != JSON_TYPE.OBJECT)
496             typeMismatch("object");
497 
498         static if (is(T == class))
499         {
500             result = new T();
501         }
502 
503         foreach(i, ref v; result.tupleof) {
504             immutable fieldName = getFieldName!(T, i);
505             auto field = fieldName in value.object;
506             if (field) {
507                 v = fromJSONValue!(typeof(v))(*field, decoder);
508             } else {
509                 if (decoder._missingHandler)
510                     decoder._missingHandler(fieldName);
511             }
512         }
513     }
514 
515     return result;
516 }