1 /* 2 * Copyright (c) 2009-2016 Petri Lehtinen <petri@digip.org> 3 * 4 * Jansson is free software; you can redistribute it and/or modify 5 * it under the terms of the MIT license. See LICENSE for details. 6 */ 7 /** 8 * License: MIT 9 */ 10 module jansson_d.dump; 11 12 13 package: 14 15 private static import core.stdc.stdio; 16 private static import core.stdc.stdlib; 17 private static import core.stdc.string; 18 private static import core.sys.posix.sys.types; 19 private static import core.sys.posix.unistd; 20 private static import jansson_d.hashtable; 21 private static import jansson_d.jansson; 22 private static import jansson_d.jansson_private; 23 private static import jansson_d.strbuffer; 24 private static import jansson_d.utf; 25 private static import jansson_d.value; 26 27 enum MAX_INTEGER_STR_LENGTH = 100; 28 enum MAX_REAL_STR_LENGTH = 100; 29 30 private template FLAGS_TO_INDENT(string f) 31 { 32 enum FLAGS_TO_INDENT = "(" ~ f ~ " &0x1F)"; 33 } 34 35 private template FLAGS_TO_PRECISION(string f) 36 { 37 enum FLAGS_TO_PRECISION = "((" ~ f ~ " >> 11) & 0x1F)"; 38 } 39 40 private struct buffer_ 41 { 42 const size_t size; 43 size_t used; 44 char* data; 45 } 46 47 extern (C) 48 nothrow @trusted @nogc 49 private int dump_to_strbuffer(scope const char* buffer, size_t size, scope void* data) 50 51 do 52 { 53 return jansson_d.strbuffer.strbuffer_append_bytes(cast(jansson_d.strbuffer.strbuffer_t*)(data), buffer, size); 54 } 55 56 extern (C) 57 pure nothrow @trusted @nogc @live 58 private int dump_to_buffer(scope const char* buffer, size_t size, scope void* data) 59 60 in 61 { 62 assert(buffer != null); 63 assert(data != null); 64 } 65 66 do 67 { 68 .buffer_* buf = cast(.buffer_*)(data); 69 70 if ((buf.used + size) <= buf.size) { 71 core.stdc..string.memcpy(&buf.data[buf.used], buffer, size); 72 } 73 74 buf.used += size; 75 76 return 0; 77 } 78 79 extern (C) 80 nothrow @nogc @live 81 private int dump_to_file(scope const char* buffer, size_t size, void* data) 82 83 in 84 { 85 assert(buffer != null); 86 assert(data != null); 87 } 88 89 do 90 { 91 core.stdc.stdio.FILE* dest = cast(core.stdc.stdio.FILE*)(data); 92 93 if (core.stdc.stdio.fwrite(buffer, size, 1, dest) != 1) { 94 return -1; 95 } 96 97 return 0; 98 } 99 100 extern (C) 101 nothrow @nogc @live 102 private int dump_to_fd(scope const char* buffer, size_t size, scope void* data) 103 104 in 105 { 106 assert(buffer != null); 107 assert(data != null); 108 } 109 110 do 111 { 112 static if (__traits(compiles, core.sys.posix.unistd.write)) { 113 int* dest = cast(int*)(data); 114 115 if (core.sys.posix.unistd.write(*dest, buffer, size) == cast(core.sys.posix.sys.types.ssize_t)(size)) { 116 return 0; 117 } 118 } 119 120 return -1; 121 } 122 123 /* 32 spaces (the maximum indentation size) */ 124 private static immutable char[] whitespace = " \0"; 125 126 nothrow @nogc @live 127 private int dump_indent(size_t flags, int depth, int space, jansson_d.jansson.json_dump_callback_t dump, void* data) 128 129 in 130 { 131 assert(dump != null); 132 } 133 134 do 135 { 136 if (mixin (.FLAGS_TO_INDENT!("flags")) > 0) { 137 uint ws_count = mixin (.FLAGS_TO_INDENT!("flags")); 138 uint n_spaces = depth * ws_count; 139 140 if (dump("\n", 1, data)) { 141 return -1; 142 } 143 144 while (n_spaces > 0) { 145 int cur_n = (n_spaces < (.whitespace.length - 1)) ? (n_spaces) : (.whitespace.length - 1); 146 147 if (dump(&(.whitespace[0]), cur_n, data)) { 148 return -1; 149 } 150 151 n_spaces -= cur_n; 152 } 153 } else if ((space) && (!(flags & jansson_d.jansson.JSON_COMPACT))) { 154 return dump(" ", 1, data); 155 } 156 157 return 0; 158 } 159 160 nothrow @nogc @live 161 private int dump_string(scope const char* str, size_t len, jansson_d.jansson.json_dump_callback_t dump, void* data, size_t flags) 162 163 in 164 { 165 assert(str != null); 166 assert(dump != null); 167 } 168 169 do 170 { 171 if (dump("\"", 1, data)) { 172 return -1; 173 } 174 175 const (char)* str_temp = str; 176 const (char)* pos = str_temp; 177 const (char)* end = str_temp; 178 const (char)* lim = str_temp + len; 179 int codepoint = 0; 180 181 while (true) { 182 while (end < lim) { 183 end = jansson_d.utf.utf8_iterate(pos, lim - pos, &codepoint); 184 185 if (end == null) { 186 return -1; 187 } 188 189 /* mandatory escape or control char */ 190 if ((codepoint == '\\') || (codepoint == '"') || (codepoint < 0x20)) { 191 break; 192 } 193 194 /* slash */ 195 if ((flags & jansson_d.jansson.JSON_ESCAPE_SLASH) && (codepoint == '/')) { 196 break; 197 } 198 199 /* non-ASCII */ 200 if ((flags & jansson_d.jansson.JSON_ENSURE_ASCII) && (codepoint > 0x7F)) { 201 break; 202 } 203 204 pos = end; 205 } 206 207 if (pos != str_temp) { 208 if (dump(str_temp, pos - str_temp, data)) { 209 return -1; 210 } 211 } 212 213 if (end == pos) { 214 break; 215 } 216 217 /* handle \, /, ", and control codes */ 218 int length_ = 2; 219 220 const (char)* text = void; 221 222 switch (codepoint) { 223 case '\\': 224 text = "\\\\"; 225 226 break; 227 228 case '\"': 229 text = "\\\""; 230 231 break; 232 233 case '\b': 234 text = "\\b"; 235 236 break; 237 238 case '\f': 239 text = "\\f"; 240 241 break; 242 243 case '\n': 244 text = "\\n"; 245 246 break; 247 248 case '\r': 249 text = "\\r"; 250 251 break; 252 253 case '\t': 254 text = "\\t"; 255 256 break; 257 258 case '/': 259 text = "\\/"; 260 261 break; 262 263 default: 264 char[13] seq = void; 265 266 /* codepoint is in BMP */ 267 if (codepoint < 0x010000) { 268 jansson_d.jansson_private.snprintf(&(seq[0]), seq.length, "\\u%04X", cast(uint)(codepoint)); 269 length_ = 6; 270 } else { 271 /* not in BMP . construct a UTF-16 surrogate pair */ 272 273 codepoint -= 0x010000; 274 int first = 0xD800 | ((codepoint & 0x0FFC00) >> 10); 275 int last = 0xDC00 | (codepoint & 0x0003FF); 276 277 jansson_d.jansson_private.snprintf(&(seq[0]), seq.length, "\\u%04X\\u%04X", cast(uint)(first), cast(uint)(last)); 278 length_ = 12; 279 } 280 281 text = &(seq[0]); 282 283 break; 284 } 285 286 if (dump(text, length_, data)) { 287 return -1; 288 } 289 290 pos = end; 291 str_temp = end; 292 } 293 294 return dump("\"", 1, data); 295 } 296 297 private struct key_len_ 298 { 299 const (char)* key; 300 int len; 301 } 302 303 extern (C) 304 pure nothrow @trusted @nogc @live 305 private int compare_keys(scope const void* key1, scope const void* key2) 306 307 in 308 { 309 assert(key1 != null); 310 assert(key2 != null); 311 } 312 313 do 314 { 315 const .key_len_* k1 = cast(const .key_len_*)(key1); 316 const .key_len_* k2 = cast(const .key_len_*)(key2); 317 const size_t min_size = (k1.len < k2.len) ? (k1.len) : (k2.len); 318 int res = core.stdc..string.memcmp(k1.key, k2.key, min_size); 319 320 if (res != 0) { 321 return res; 322 } 323 324 return k1.len - k2.len; 325 } 326 327 nothrow @trusted @nogc 328 private int do_dump(scope const jansson_d.jansson.json_t* json, size_t flags, int depth, scope jansson_d.hashtable.hashtable_t* parents, jansson_d.jansson.json_dump_callback_t dump, void* data) 329 330 in 331 { 332 assert(dump != null); 333 } 334 335 do 336 { 337 int embed = flags & jansson_d.jansson.JSON_EMBED; 338 339 flags &= ~jansson_d.jansson.JSON_EMBED; 340 341 if (json == null) { 342 return -1; 343 } 344 345 switch (mixin (jansson_d.jansson.json_typeof!("json"))) { 346 case jansson_d.jansson.json_type.JSON_NULL: 347 return dump("null", 4, data); 348 349 case jansson_d.jansson.json_type.JSON_TRUE: 350 return dump("true", 4, data); 351 352 case jansson_d.jansson.json_type.JSON_FALSE: 353 return dump("false", 5, data); 354 355 case jansson_d.jansson.json_type.JSON_INTEGER: 356 char[.MAX_INTEGER_STR_LENGTH] buffer = void; 357 int size = jansson_d.jansson_private.snprintf(&(buffer[0]), buffer.length, "%" ~ jansson_d.jansson.JSON_INTEGER_FORMAT, jansson_d.value.json_integer_value(json)); 358 359 if ((size < 0) || (size >= buffer.length)) { 360 return -1; 361 } 362 363 return dump(&(buffer[0]), size, data); 364 365 case jansson_d.jansson.json_type.JSON_REAL: 366 char[.MAX_REAL_STR_LENGTH] buffer = void; 367 double value = jansson_d.value.json_real_value(json); 368 int size = jansson_d.jansson_private.jsonp_dtostr(&(buffer[0]), buffer.length, value, mixin (.FLAGS_TO_PRECISION!("flags"))); 369 370 if (size < 0) { 371 return -1; 372 } 373 374 return dump(&(buffer[0]), size, data); 375 376 case jansson_d.jansson.json_type.JSON_STRING: 377 return .dump_string(jansson_d.value.json_string_value(json), jansson_d.value.json_string_length(json), dump, data, flags); 378 379 case jansson_d.jansson.json_type.JSON_ARRAY: 380 /* 381 * Space for "0x", double the sizeof a pointer for the hex and a 382 * terminator. 383 */ 384 char[2 + (json.sizeof * 2) + 1] key = void; 385 size_t key_len = void; 386 387 /* detect circular references */ 388 if (jansson_d.jansson_private.jsonp_loop_check(parents, json, &(key[0]), key.length, &key_len)) { 389 return -1; 390 } 391 392 size_t n = jansson_d.value.json_array_size(json); 393 394 if ((!embed) && (dump("[", 1, data))) { 395 return -1; 396 } 397 398 if (n == 0) { 399 jansson_d.hashtable.hashtable_del(parents, &(key[0]), key_len); 400 401 return (embed) ? (0) : (dump("]", 1, data)); 402 } 403 404 if (.dump_indent(flags, depth + 1, 0, dump, data)) { 405 return -1; 406 } 407 408 for (size_t i = 0; i < n; ++i) { 409 if (.do_dump(jansson_d.value.json_array_get(json, i), flags, depth + 1, parents, dump, data)) { 410 return -1; 411 } 412 413 if (i < (n - 1)) { 414 if ((dump(",", 1, data)) || (.dump_indent(flags, depth + 1, 1, dump, data))) { 415 return -1; 416 } 417 } else { 418 if (.dump_indent(flags, depth, 0, dump, data)) { 419 return -1; 420 } 421 } 422 } 423 424 jansson_d.hashtable.hashtable_del(parents, &(key[0]), key_len); 425 426 return (embed) ? (0) : (dump("]", 1, data)); 427 428 case jansson_d.jansson.json_type.JSON_OBJECT: 429 const (char)* separator = void; 430 int separator_length = void; 431 432 if (flags & jansson_d.jansson.JSON_COMPACT) { 433 separator = ":"; 434 separator_length = 1; 435 } else { 436 separator = ": "; 437 separator_length = 2; 438 } 439 440 char[jansson_d.jansson_private.LOOP_KEY_LEN] loop_key = void; 441 size_t loop_key_len = void; 442 443 /* detect circular references */ 444 if (jansson_d.jansson_private.jsonp_loop_check(parents, json, &(loop_key[0]), loop_key.length, &loop_key_len)) { 445 return -1; 446 } 447 448 void* iter = jansson_d.value.json_object_iter(cast(jansson_d.jansson.json_t*)(json)); 449 450 if ((!embed) && (dump("{", 1, data))) { 451 return -1; 452 } 453 454 if (iter == null) { 455 jansson_d.hashtable.hashtable_del(parents, &(loop_key[0]), loop_key_len); 456 457 return (embed) ? (0) : (dump("}", 1, data)); 458 } 459 460 if (.dump_indent(flags, depth + 1, 0, dump, data)) { 461 return -1; 462 } 463 464 if (flags & jansson_d.jansson.JSON_SORT_KEYS) { 465 size_t size = jansson_d.value.json_object_size(json); 466 .key_len_* keys = cast(.key_len_*)(jansson_d.jansson_private.jsonp_malloc(size * .key_len_.sizeof)); 467 468 if (keys == null) { 469 return -1; 470 } 471 472 size_t i = 0; 473 474 while (iter != null) { 475 .key_len_* keylen = &keys[i]; 476 477 keylen.key = jansson_d.value.json_object_iter_key(iter); 478 keylen.len = cast(int)(jansson_d.value.json_object_iter_key_len(iter)); 479 480 iter = jansson_d.value.json_object_iter_next(cast(jansson_d.jansson.json_t*)(json), iter); 481 i++; 482 } 483 484 assert(i == size); 485 486 core.stdc.stdlib.qsort(keys, size, .key_len_.sizeof, &.compare_keys); 487 488 for (i = 0; i < size; i++) { 489 const .key_len_* key = &keys[i]; 490 jansson_d.jansson.json_t* value = jansson_d.value.json_object_getn(json, key.key, key.len); 491 assert(value); 492 493 .dump_string(key.key, key.len, dump, data, flags); 494 495 if ((dump(separator, separator_length, data)) || (.do_dump(value, flags, depth + 1, parents, dump, data))) { 496 jansson_d.jansson_private.jsonp_free(keys); 497 498 return -1; 499 } 500 501 if (i < (size - 1)) { 502 if ((dump(",", 1, data)) || (.dump_indent(flags, depth + 1, 1, dump, data))) { 503 jansson_d.jansson_private.jsonp_free(keys); 504 505 return -1; 506 } 507 } else { 508 if (.dump_indent(flags, depth, 0, dump, data)) { 509 jansson_d.jansson_private.jsonp_free(keys); 510 511 return -1; 512 } 513 } 514 } 515 516 jansson_d.jansson_private.jsonp_free(keys); 517 } else { 518 /* Don't sort keys */ 519 520 while (iter != null) { 521 void* next = jansson_d.value.json_object_iter_next(cast(jansson_d.jansson.json_t*)(json), iter); 522 const char* key = jansson_d.value.json_object_iter_key(iter); 523 const size_t key_len = jansson_d.value.json_object_iter_key_len(iter); 524 525 .dump_string(key, key_len, dump, data, flags); 526 527 if ((dump(separator, separator_length, data)) || (.do_dump(jansson_d.value.json_object_iter_value(iter), flags, depth + 1, parents, dump, data))) { 528 return -1; 529 } 530 531 if (next != null) { 532 if ((dump(",", 1, data)) || (.dump_indent(flags, depth + 1, 1, dump, data))) { 533 return -1; 534 } 535 } else { 536 if (.dump_indent(flags, depth, 0, dump, data)) { 537 return -1; 538 } 539 } 540 541 iter = next; 542 } 543 } 544 545 jansson_d.hashtable.hashtable_del(parents, &(loop_key[0]), loop_key_len); 546 547 return (embed) ? (0) : (dump("}", 1, data)); 548 549 default: 550 /* not reached */ 551 return -1; 552 } 553 } 554 555 /// 556 extern (C) 557 nothrow @trusted @nogc @live //ToDo: @nodiscard 558 public char* json_dumps(scope const jansson_d.jansson.json_t* json, size_t flags) 559 560 do 561 { 562 jansson_d.strbuffer.strbuffer_t strbuff = void; 563 564 if (jansson_d.strbuffer.strbuffer_init(&strbuff)) { 565 return null; 566 } 567 568 char* result = void; 569 570 if (.json_dump_callback(json, &.dump_to_strbuffer, cast(void*)(&strbuff), flags)) { 571 result = null; 572 } else { 573 result = jansson_d.jansson_private.jsonp_strdup(jansson_d.strbuffer.strbuffer_value(&strbuff)); 574 } 575 576 jansson_d.strbuffer.strbuffer_close(&strbuff); 577 578 return result; 579 } 580 581 /// 582 extern (C) 583 nothrow @trusted @nogc @live 584 public size_t json_dumpb(scope const jansson_d.jansson.json_t* json, scope char* buffer, size_t size, size_t flags) 585 586 do 587 { 588 .buffer_ buf = {size, 0, buffer}; 589 590 if (.json_dump_callback(json, &.dump_to_buffer, cast(void*)(&buf), flags)) { 591 return 0; 592 } 593 594 return buf.used; 595 } 596 597 /// 598 extern (C) 599 nothrow @trusted @nogc @live 600 public int json_dumpf(scope const jansson_d.jansson.json_t* json, core.stdc.stdio.FILE* output, size_t flags) 601 602 do 603 { 604 return .json_dump_callback(json, &.dump_to_file, cast(void*)(output), flags); 605 } 606 607 /// 608 extern (C) 609 nothrow @trusted @nogc @live 610 public int json_dumpfd(scope const jansson_d.jansson.json_t* json, int output, size_t flags) 611 612 do 613 { 614 return .json_dump_callback(json, &.dump_to_fd, cast(void*)(&output), flags); 615 } 616 617 /// 618 extern (C) 619 nothrow @nogc @live 620 public int json_dump_file(scope const jansson_d.jansson.json_t* json, scope const char* path, size_t flags) 621 622 do 623 { 624 core.stdc.stdio.FILE* output = core.stdc.stdio.fopen(path, "w"); 625 626 if (output == null) { 627 return -1; 628 } 629 630 int result = .json_dumpf(json, output, flags); 631 632 if (core.stdc.stdio.fclose(output) != 0) { 633 return -1; 634 } 635 636 return result; 637 } 638 639 /// 640 extern (C) 641 nothrow @trusted @nogc 642 public int json_dump_callback(scope const jansson_d.jansson.json_t* json, jansson_d.jansson.json_dump_callback_t callback, void* data, size_t flags) 643 644 do 645 { 646 if (!(flags & jansson_d.jansson.JSON_ENCODE_ANY)) { 647 if ((!mixin (jansson_d.jansson.json_is_array!("json"))) && (!mixin (jansson_d.jansson.json_is_object!("json")))) { 648 return -1; 649 } 650 } 651 652 jansson_d.hashtable.hashtable_t parents_set = void; 653 654 if (jansson_d.hashtable.hashtable_init(&parents_set)) { 655 return -1; 656 } 657 658 int res = .do_dump(json, flags, 0, &parents_set, callback, data); 659 jansson_d.hashtable.hashtable_close(&parents_set); 660 661 return res; 662 }