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 }