UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "cx/json.h" 30 #include "cx/kv_list.h" 31 32 #include <string.h> 33 #include <assert.h> 34 #include <stdio.h> 35 #include <inttypes.h> 36 #include <ctype.h> 37 38 /* 39 * RFC 8259 40 * https://tools.ietf.org/html/rfc8259 41 */ 42 43 static CxJsonValue cx_json_value_nothing = {.type = CX_JSON_NOTHING}; 44 45 static void token_destroy(CxJsonToken *token) { 46 if (token->allocated) { 47 cx_strfree(&token->content); 48 token->allocated = false; 49 } 50 } 51 52 static int num_isexp(const char *content, size_t length, size_t pos) { 53 if (pos >= length) { 54 return 0; 55 } 56 57 int ok = 0; 58 for (size_t i = pos; i < length; i++) { 59 char c = content[i]; 60 if (isdigit((unsigned char)c)) { 61 ok = 1; 62 } else if (i == pos) { 63 if (!(c == '+' || c == '-')) { 64 return 0; 65 } 66 } else { 67 return 0; 68 } 69 } 70 71 return ok; 72 } 73 74 static CxJsonTokenType token_numbertype(const char *content, size_t length) { 75 if (length == 0) return CX_JSON_TOKEN_ERROR; 76 77 if (content[0] != '-' && !isdigit((unsigned char)content[0])) { 78 return CX_JSON_TOKEN_ERROR; 79 } 80 81 CxJsonTokenType type = CX_JSON_TOKEN_INTEGER; 82 for (size_t i = 1; i < length; i++) { 83 if (content[i] == '.') { 84 if (type == CX_JSON_TOKEN_NUMBER) { 85 return CX_JSON_TOKEN_ERROR; // more than one decimal separator 86 } 87 type = CX_JSON_TOKEN_NUMBER; 88 } else if (content[i] == 'e' || content[i] == 'E') { 89 return num_isexp(content, length, i + 1) ? CX_JSON_TOKEN_NUMBER : CX_JSON_TOKEN_ERROR; 90 } else if (!isdigit((unsigned char)content[i])) { 91 return CX_JSON_TOKEN_ERROR; // char is not a digit, decimal separator or exponent sep 92 } 93 } 94 95 return type; 96 } 97 98 static CxJsonToken token_create(CxJson *json, bool isstring, size_t start, size_t end) { 99 cxmutstr str = cx_mutstrn(json->buffer.space + start, end - start); 100 bool allocated = false; 101 if (json->uncompleted.tokentype != CX_JSON_NO_TOKEN) { 102 allocated = true; 103 str = cx_strcat_m(json->uncompleted.content, 1, str); 104 if (str.ptr == NULL) { // LCOV_EXCL_START 105 return (CxJsonToken){CX_JSON_NO_TOKEN, false, {NULL, 0}}; 106 } // LCOV_EXCL_STOP 107 } 108 json->uncompleted = (CxJsonToken){0}; 109 CxJsonTokenType ttype; 110 if (isstring) { 111 ttype = CX_JSON_TOKEN_STRING; 112 } else { 113 cxstring s = cx_strcast(str); 114 if (!cx_strcmp(s, CX_STR("true")) || !cx_strcmp(s, CX_STR("false")) 115 || !cx_strcmp(s, CX_STR("null"))) { 116 ttype = CX_JSON_TOKEN_LITERAL; 117 } else { 118 ttype = token_numbertype(str.ptr, str.length); 119 } 120 } 121 if (ttype == CX_JSON_TOKEN_ERROR) { 122 if (allocated) { 123 cx_strfree(&str); 124 } 125 return (CxJsonToken){CX_JSON_TOKEN_ERROR, false, {NULL, 0}}; 126 } 127 return (CxJsonToken){ttype, allocated, str}; 128 } 129 130 static CxJsonTokenType char2ttype(char c) { 131 switch (c) { 132 case '[': { 133 return CX_JSON_TOKEN_BEGIN_ARRAY; 134 } 135 case '{': { 136 return CX_JSON_TOKEN_BEGIN_OBJECT; 137 } 138 case ']': { 139 return CX_JSON_TOKEN_END_ARRAY; 140 } 141 case '}': { 142 return CX_JSON_TOKEN_END_OBJECT; 143 } 144 case ':': { 145 return CX_JSON_TOKEN_NAME_SEPARATOR; 146 } 147 case ',': { 148 return CX_JSON_TOKEN_VALUE_SEPARATOR; 149 } 150 case '""': { 151 return CX_JSON_TOKEN_STRING; 152 } 153 default: { 154 if (isspace((unsigned char)c)) { 155 return CX_JSON_TOKEN_SPACE; 156 } 157 } 158 } 159 return CX_JSON_NO_TOKEN; 160 } 161 162 static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) { 163 // check if there is data in the buffer 164 if (cxBufferEof(&json->buffer)) { 165 return json->uncompleted.tokentype == CX_JSON_NO_TOKEN ? 166 CX_JSON_NO_DATA : CX_JSON_INCOMPLETE_DATA; 167 } 168 169 // current token type and start index 170 CxJsonTokenType ttype = json->uncompleted.tokentype; 171 size_t token_part_start = json->buffer.pos; 172 173 bool escape_end_of_string = ttype == CX_JSON_TOKEN_STRING 174 && json->uncompleted.content.ptr[json->uncompleted.content.length-1] == '\\'; 175 176 for (size_t i = json->buffer.pos; i < json->buffer.size; i++) { 177 char c = json->buffer.space[i]; 178 if (ttype != CX_JSON_TOKEN_STRING) { 179 // currently non-string token 180 CxJsonTokenType ctype = char2ttype(c); // start of new token? 181 if (ttype == CX_JSON_NO_TOKEN) { 182 if (ctype == CX_JSON_TOKEN_SPACE) { 183 json->buffer.pos++; 184 continue; 185 } else if (ctype == CX_JSON_TOKEN_STRING) { 186 // begin string 187 ttype = CX_JSON_TOKEN_STRING; 188 token_part_start = i; 189 } else if (ctype != CX_JSON_NO_TOKEN) { 190 // single-char token 191 json->buffer.pos = i + 1; 192 *result = (CxJsonToken){ctype, false, {NULL, 0}}; 193 return CX_JSON_NO_ERROR; 194 } else { 195 ttype = CX_JSON_TOKEN_LITERAL; // number or literal 196 token_part_start = i; 197 } 198 } else { 199 // finish token 200 if (ctype != CX_JSON_NO_TOKEN) { 201 *result = token_create(json, false, token_part_start, i); 202 if (result->tokentype == CX_JSON_NO_TOKEN) { 203 return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE 204 } 205 if (result->tokentype == CX_JSON_TOKEN_ERROR) { 206 return CX_JSON_FORMAT_ERROR_NUMBER; 207 } 208 json->buffer.pos = i; 209 return CX_JSON_NO_ERROR; 210 } 211 } 212 } else { 213 // currently inside a string 214 if (escape_end_of_string) { 215 escape_end_of_string = false; 216 } else { 217 if (c == '""') { 218 *result = token_create(json, true, token_part_start, i + 1); 219 if (result->tokentype == CX_JSON_NO_TOKEN) { 220 return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE 221 } 222 json->buffer.pos = i + 1; 223 return CX_JSON_NO_ERROR; 224 } else if (c == '\\') { 225 escape_end_of_string = true; 226 } 227 } 228 } 229 } 230 231 if (ttype == CX_JSON_NO_TOKEN) { 232 return CX_JSON_NO_DATA; 233 } else { 234 // uncompleted token 235 size_t uncompleted_len = json->buffer.size - token_part_start; 236 if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) { 237 // current token is uncompleted 238 // save current token content 239 CxJsonToken uncompleted = { 240 ttype, true, 241 cx_strdup(cx_strn(json->buffer.space + token_part_start, uncompleted_len)) 242 }; 243 if (uncompleted.content.ptr == NULL) { 244 return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE 245 } 246 json->uncompleted = uncompleted; 247 } else { 248 // previously we also had an uncompleted token 249 // combine the uncompleted token with the current token 250 assert(json->uncompleted.allocated); 251 cxmutstr str = cx_strcat_m(json->uncompleted.content, 1, 252 cx_strn(json->buffer.space + token_part_start, uncompleted_len)); 253 if (str.ptr == NULL) { 254 return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE 255 } 256 json->uncompleted.content = str; 257 } 258 // advance the buffer position - we saved the stuff in the uncompleted token 259 json->buffer.pos += uncompleted_len; 260 return CX_JSON_INCOMPLETE_DATA; 261 } 262 } 263 264 // converts a Unicode codepoint to utf8 265 static unsigned codepoint_to_utf8(uint32_t codepoint, char *output_buf) { 266 if (codepoint <= 0x7F) { 267 *output_buf = (char)codepoint; 268 return 1; 269 } else if (codepoint <= 0x7FF) { 270 output_buf[0] = (char)(0xC0 | ((codepoint >> 6) & 0x1F)); 271 output_buf[1] = (char)(0x80 | (codepoint & 0x3F)); 272 return 2; 273 } else if (codepoint <= 0xFFFF) { 274 output_buf[0] = (char)(0xE0 | ((codepoint >> 12) & 0x0F)); 275 output_buf[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); 276 output_buf[2] = (char)(0x80 | (codepoint & 0x3F)); 277 return 3; 278 } else if (codepoint <= 0x10FFFF) { 279 output_buf[0] = (char)(0xF0 | ((codepoint >> 18) & 0x07)); 280 output_buf[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F)); 281 output_buf[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F)); 282 output_buf[3] = (char)(0x80 | (codepoint & 0x3F)); 283 return 4; 284 } 285 286 return 0; // LCOV_EXCL_LINE 287 } 288 289 // converts a utf16 surrogate pair to utf8 290 static inline uint32_t utf16pair_to_codepoint(uint16_t c0, uint16_t c1) { 291 return ((c0 - 0xD800) << 10) + (c1 - 0xDC00) + 0x10000; 292 } 293 294 static unsigned unescape_unicode_string(cxstring str, char *utf8buf) { 295 // str is supposed to start with "\uXXXX" or "\uXXXX\uXXXX" 296 // remaining bytes in the string are ignored (str may be larger!) 297 298 if (str.length < 6 || str.ptr[0] != '\\' || str.ptr[1] != 'u') { 299 return 0; 300 } 301 302 unsigned utf8len = 0; 303 cxstring ustr1 = { str.ptr + 2, 4}; 304 uint16_t utf16a, utf16b; 305 if (!cx_strtou16_lc(ustr1, &utf16a, 16, "")) { 306 uint32_t codepoint; 307 if (utf16a < 0xD800 || utf16a > 0xE000) { 308 // character is in the Basic Multilingual Plane 309 // and encoded as a single utf16 char 310 codepoint = utf16a; 311 utf8len = codepoint_to_utf8(codepoint, utf8buf); 312 } else if (utf16a >= 0xD800 && utf16a <= 0xDBFF) { 313 // character is encoded as a surrogate pair 314 // get next 6 bytes 315 if (str.length >= 12) { 316 if (str.ptr[6] == '\\' && str.ptr[7] == 'u') { 317 cxstring ustr2 = { str.ptr+8, 4 }; 318 if (!cx_strtou16_lc(ustr2, &utf16b, 16, "") 319 && utf16b >= 0xDC00 && utf16b <= 0xDFFF) { 320 codepoint = utf16pair_to_codepoint(utf16a, utf16b); 321 utf8len = codepoint_to_utf8(codepoint, utf8buf); 322 } 323 } 324 } 325 } 326 } 327 return utf8len; 328 } 329 330 static cxmutstr unescape_string(const CxAllocator *a, cxmutstr str) { 331 // note: this function expects that str contains the enclosing quotes! 332 333 cxmutstr result; 334 result.length = 0; 335 result.ptr = cxMalloc(a, str.length - 1); 336 if (result.ptr == NULL) return result; // LCOV_EXCL_LINE 337 338 bool u = false; 339 for (size_t i = 1; i < str.length - 1; i++) { 340 char c = str.ptr[i]; 341 if (u) { 342 u = false; 343 if (c == 'n') { 344 c = '\n'; 345 } else if (c == '""') { 346 c = '""'; 347 } else if (c == 't') { 348 c = '\t'; 349 } else if (c == 'r') { 350 c = '\r'; 351 } else if (c == '\\') { 352 c = '\\'; 353 } else if (c == '/') { 354 c = '/'; // always unescape, we don't need settings here 355 } else if (c == 'f') { 356 c = '\f'; 357 } else if (c == 'b') { 358 c = '\b'; 359 } else if (c == 'u') { 360 char utf8buf[4]; 361 unsigned utf8len = unescape_unicode_string( 362 cx_strn(str.ptr + i - 1, str.length - i), 363 utf8buf 364 ); 365 if(utf8len > 0) { 366 i += utf8len < 4 ? 4 : 10; 367 // add all bytes from utf8buf except the last char 368 // to the result (last char will be added below) 369 utf8len--; 370 c = utf8buf[utf8len]; 371 for (unsigned x = 0; x < utf8len; x++) { 372 result.ptr[result.length++] = utf8buf[x]; 373 } 374 } else { 375 // decoding failed, ignore the entire sequence 376 result.ptr[result.length++] = '\\'; 377 } 378 } else { 379 // TODO: discuss the behavior for unrecognized escape sequences 380 // most parsers throw an error here - we just ignore it 381 result.ptr[result.length++] = '\\'; // LCOV_EXCL_LINE 382 } 383 384 result.ptr[result.length++] = c; 385 } else { 386 if (c == '\\') { 387 u = true; 388 } else { 389 result.ptr[result.length++] = c; 390 } 391 } 392 } 393 result.ptr[result.length] = 0; 394 395 return result; 396 } 397 398 static cxmutstr escape_string(cxstring str, bool escape_slash) { 399 // note: this function produces the string without enclosing quotes 400 // the reason is that we don't want to allocate memory just for that 401 CxBuffer buf = {0}; 402 403 bool all_printable = true; 404 for (size_t i = 0; i < str.length; i++) { 405 unsigned char c = str.ptr[i]; 406 bool escape = c < 0x20 || c == '\\' || c == '""' 407 || (escape_slash && c == '/'); 408 409 if (all_printable && escape) { 410 size_t capa = str.length + 32; 411 char *space = cxMallocDefault(capa); 412 if (space == NULL) return cx_mutstrn(NULL, 0); 413 cxBufferInit(&buf, space, capa, NULL, CX_BUFFER_AUTO_EXTEND); 414 cxBufferWrite(str.ptr, 1, i, &buf); 415 all_printable = false; 416 } 417 if (escape) { 418 cxBufferPut(&buf, '\\'); 419 if (c == '\"') { 420 cxBufferPut(&buf, '\"'); 421 } else if (c == '\n') { 422 cxBufferPut(&buf, 'n'); 423 } else if (c == '\t') { 424 cxBufferPut(&buf, 't'); 425 } else if (c == '\r') { 426 cxBufferPut(&buf, 'r'); 427 } else if (c == '\\') { 428 cxBufferPut(&buf, '\\'); 429 } else if (c == '/') { 430 cxBufferPut(&buf, '/'); 431 } else if (c == '\f') { 432 cxBufferPut(&buf, 'f'); 433 } else if (c == '\b') { 434 cxBufferPut(&buf, 'b'); 435 } else { 436 char code[6]; 437 snprintf(code, sizeof(code), "u%04x", (unsigned int) c); 438 cxBufferPutString(&buf, code); 439 } 440 } else if (!all_printable) { 441 cxBufferPut(&buf, c); 442 } 443 } 444 cxmutstr ret; 445 if (all_printable) { 446 // don't copy the string when we don't need to escape anything 447 ret = cx_mutstrn((char*)str.ptr, str.length); 448 } else { 449 ret = cx_mutstrn(buf.space, buf.size); 450 } 451 cxBufferDestroy(&buf); 452 return ret; 453 } 454 455 static CxJsonObject json_create_object_map(const CxAllocator *allocator) { 456 // TODO: we might want to add a comparator that is sorting the elements by their key 457 CxMap *map = cxKvListCreateAsMap(allocator, NULL, CX_STORE_POINTERS); 458 if (map == NULL) return NULL; // LCOV_EXCL_LINE 459 cxDefineDestructor(map, cxJsonValueFree); 460 return map; 461 } 462 463 static void json_free_object_map(CxJsonObject obj) { 464 cxMapFree(obj); 465 } 466 467 static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) { 468 CxJsonValue *v = cxCalloc(json->allocator, 1, sizeof(CxJsonValue)); 469 if (v == NULL) return NULL; // LCOV_EXCL_LINE 470 471 // initialize the value 472 v->type = type; 473 v->allocator = json->allocator; 474 if (type == CX_JSON_ARRAY) { 475 cx_array_initialize_a(json->allocator, v->array.data, 16); 476 if (v->array.data == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE 477 } else if (type == CX_JSON_OBJECT) { 478 v->object = json_create_object_map(json->allocator); 479 if (v->object == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE 480 } 481 482 // add the new value to a possible parent 483 if (json->vbuf_size > 0) { 484 CxJsonValue *parent = json->vbuf[json->vbuf_size - 1]; 485 assert(parent != NULL); 486 if (parent->type == CX_JSON_ARRAY) { 487 CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL); 488 if (cx_array_simple_add_a(&value_realloc, parent->array.data, v)) { 489 goto create_json_value_exit_error; // LCOV_EXCL_LINE 490 } 491 } else if (parent->type == CX_JSON_OBJECT) { 492 // the member was already created after parsing the name 493 // store the pointer of the uncompleted value in the map 494 assert(json->uncompleted_member_name.ptr != NULL); 495 if (cxMapPut(parent->object, json->uncompleted_member_name, v)) { 496 goto create_json_value_exit_error; // LCOV_EXCL_LINE 497 } 498 cx_strfree_a(json->allocator, &json->uncompleted_member_name); 499 } else { 500 assert(false); // LCOV_EXCL_LINE 501 } 502 } 503 504 // add the new value to the stack, if it is an array or object 505 if (type == CX_JSON_ARRAY || type == CX_JSON_OBJECT) { 506 CxArrayReallocator vbuf_realloc = cx_array_reallocator(NULL, json->vbuf_internal); 507 if (cx_array_simple_add_a(&vbuf_realloc, json->vbuf, v)) { 508 goto create_json_value_exit_error; // LCOV_EXCL_LINE 509 } 510 } 511 512 // if currently no value is parsed, this is now the value of interest 513 if (json->parsed == NULL) { 514 json->parsed = v; 515 } 516 517 return v; 518 // LCOV_EXCL_START 519 create_json_value_exit_error: 520 cxJsonValueFree(v); 521 return NULL; 522 // LCOV_EXCL_STOP 523 } 524 525 #define JP_STATE_VALUE_BEGIN 0 526 #define JP_STATE_VALUE_END 10 527 #define JP_STATE_VALUE_BEGIN_OBJ 1 528 #define JP_STATE_OBJ_SEP_OR_CLOSE 11 529 #define JP_STATE_VALUE_BEGIN_AR 2 530 #define JP_STATE_ARRAY_SEP_OR_CLOSE 12 531 #define JP_STATE_OBJ_NAME_OR_CLOSE 5 532 #define JP_STATE_OBJ_NAME 6 533 #define JP_STATE_OBJ_COLON 7 534 535 void cxJsonInit(CxJson *json, const CxAllocator *allocator) { 536 if (allocator == NULL) { 537 allocator = cxDefaultAllocator; 538 } 539 540 memset(json, 0, sizeof(CxJson)); 541 json->allocator = allocator; 542 543 json->states = json->states_internal; 544 json->states_capacity = cx_nmemb(json->states_internal); 545 json->states[0] = JP_STATE_VALUE_BEGIN; 546 json->states_size = 1; 547 548 json->vbuf = json->vbuf_internal; 549 json->vbuf_capacity = cx_nmemb(json->vbuf_internal); 550 } 551 552 void cxJsonDestroy(CxJson *json) { 553 cxBufferDestroy(&json->buffer); 554 if (json->states != json->states_internal) { 555 cxFreeDefault(json->states); 556 } 557 if (json->vbuf != json->vbuf_internal) { 558 cxFreeDefault(json->vbuf); 559 } 560 cxJsonValueFree(json->parsed); 561 json->parsed = NULL; 562 token_destroy(&json->uncompleted); 563 cx_strfree_a(json->allocator, &json->uncompleted_member_name); 564 } 565 566 void cxJsonReset(CxJson *json) { 567 const CxAllocator *allocator = json->allocator; 568 cxJsonDestroy(json); 569 cxJsonInit(json, allocator); 570 } 571 572 int cxJsonFilln(CxJson *json, const char *buf, size_t size) { 573 if (cxBufferEof(&json->buffer)) { 574 // reinitialize the buffer 575 cxBufferDestroy(&json->buffer); 576 if (buf == NULL) buf = ""; // buffer must not be initialized with NULL 577 cxBufferInit(&json->buffer, (char*) buf, size, 578 NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE); 579 json->buffer.size = size; 580 return 0; 581 } else { 582 return size != cxBufferAppend(buf, 1, size, &json->buffer); 583 } 584 } 585 586 static void json_add_state(CxJson *json, int state) { 587 // we have guaranteed the necessary space with cx_array_simple_reserve() 588 // therefore, we can safely add the state in the simplest way possible 589 json->states[json->states_size++] = state; 590 } 591 592 #define return_rec(code) \ 593 token_destroy(&token); \ 594 return code 595 596 static enum cx_json_status json_parse(CxJson *json) { 597 // Reserve a pointer for a possibly read value 598 CxJsonValue *vbuf = NULL; 599 600 // grab the next token 601 CxJsonToken token; 602 { 603 enum cx_json_status ret = token_parse_next(json, &token); 604 if (ret != CX_JSON_NO_ERROR) { 605 return ret; 606 } 607 } 608 609 // pop the current state 610 assert(json->states_size > 0); 611 int state = json->states[--json->states_size]; 612 613 // guarantee that at least two more states fit on the stack 614 CxArrayReallocator state_realloc = cx_array_reallocator(NULL, json->states_internal); 615 if (cx_array_simple_reserve_a(&state_realloc, json->states, 2)) { 616 return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE 617 } 618 619 620 // 0 JP_STATE_VALUE_BEGIN value begin 621 // 10 JP_STATE_VALUE_END expect value end 622 // 1 JP_STATE_VALUE_BEGIN_OBJ value begin (inside object) 623 // 11 JP_STATE_OBJ_SEP_OR_CLOSE object, expect separator, objclose 624 // 2 JP_STATE_VALUE_BEGIN_AR value begin (inside array) 625 // 12 JP_STATE_ARRAY_SEP_OR_CLOSE array, expect separator or arrayclose 626 // 5 JP_STATE_OBJ_NAME_OR_CLOSE object, expect name or objclose 627 // 6 JP_STATE_OBJ_NAME object, expect name 628 // 7 JP_STATE_OBJ_COLON object, expect ':' 629 630 if (state < 3) { 631 // push expected end state to the stack 632 json_add_state(json, 10 + state); 633 switch (token.tokentype) { 634 case CX_JSON_TOKEN_BEGIN_ARRAY: { 635 if (json_create_value(json, CX_JSON_ARRAY) == NULL) { 636 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 637 } 638 json_add_state(json, JP_STATE_VALUE_BEGIN_AR); 639 return_rec(CX_JSON_NO_ERROR); 640 } 641 case CX_JSON_TOKEN_BEGIN_OBJECT: { 642 if (json_create_value(json, CX_JSON_OBJECT) == NULL) { 643 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 644 } 645 json_add_state(json, JP_STATE_OBJ_NAME_OR_CLOSE); 646 return_rec(CX_JSON_NO_ERROR); 647 } 648 case CX_JSON_TOKEN_STRING: { 649 if ((vbuf = json_create_value(json, CX_JSON_STRING)) == NULL) { 650 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 651 } 652 cxmutstr str = unescape_string(json->allocator, token.content); 653 if (str.ptr == NULL) { 654 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 655 } 656 vbuf->string = str; 657 return_rec(CX_JSON_NO_ERROR); 658 } 659 case CX_JSON_TOKEN_INTEGER: 660 case CX_JSON_TOKEN_NUMBER: { 661 int type = token.tokentype == CX_JSON_TOKEN_INTEGER ? CX_JSON_INTEGER : CX_JSON_NUMBER; 662 if (NULL == (vbuf = json_create_value(json, type))) { 663 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 664 } 665 if (type == CX_JSON_INTEGER) { 666 if (cx_strtoi64(token.content, &vbuf->integer, 10)) { 667 return_rec(CX_JSON_FORMAT_ERROR_NUMBER); 668 } 669 } else { 670 if (cx_strtod(token.content, &vbuf->number)) { 671 // TODO: at the moment this is unreachable, because the tokenizer is already stricter than cx_strtod() 672 return_rec(CX_JSON_FORMAT_ERROR_NUMBER); // LCOV_EXCL_LINE 673 } 674 } 675 return_rec(CX_JSON_NO_ERROR); 676 } 677 case CX_JSON_TOKEN_LITERAL: { 678 if ((vbuf = json_create_value(json, CX_JSON_LITERAL)) == NULL) { 679 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 680 } 681 if (0 == cx_strcmp(cx_strcast(token.content), cx_str("true"))) { 682 vbuf->literal = CX_JSON_TRUE; 683 } else if (0 == cx_strcmp(cx_strcast(token.content), cx_str("false"))) { 684 vbuf->literal = CX_JSON_FALSE; 685 } else { 686 vbuf->literal = CX_JSON_NULL; 687 } 688 return_rec(CX_JSON_NO_ERROR); 689 } 690 default: { 691 return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 692 } 693 } 694 } else if (state == JP_STATE_ARRAY_SEP_OR_CLOSE) { 695 // expect ',' or ']' 696 if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) { 697 json_add_state(json, JP_STATE_VALUE_BEGIN_AR); 698 return_rec(CX_JSON_NO_ERROR); 699 } else if (token.tokentype == CX_JSON_TOKEN_END_ARRAY) { 700 // discard the array from the value buffer 701 json->vbuf_size--; 702 return_rec(CX_JSON_NO_ERROR); 703 } else { 704 return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 705 } 706 } else if (state == JP_STATE_OBJ_NAME_OR_CLOSE || state == JP_STATE_OBJ_NAME) { 707 if (state == JP_STATE_OBJ_NAME_OR_CLOSE && token.tokentype == CX_JSON_TOKEN_END_OBJECT) { 708 // discard the obj from the value buffer 709 json->vbuf_size--; 710 return_rec(CX_JSON_NO_ERROR); 711 } else { 712 // expect string 713 if (token.tokentype != CX_JSON_TOKEN_STRING) { 714 return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 715 } 716 717 // add new entry 718 cxmutstr name = unescape_string(json->allocator, token.content); 719 if (name.ptr == NULL) { 720 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE 721 } 722 assert(json->uncompleted_member_name.ptr == NULL); 723 json->uncompleted_member_name = name; 724 assert(json->vbuf_size > 0); 725 726 // next state 727 json_add_state(json, JP_STATE_OBJ_COLON); 728 return_rec(CX_JSON_NO_ERROR); 729 } 730 } else if (state == JP_STATE_OBJ_COLON) { 731 // expect ':' 732 if (token.tokentype != CX_JSON_TOKEN_NAME_SEPARATOR) { 733 return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 734 } 735 // next state 736 json_add_state(json, JP_STATE_VALUE_BEGIN_OBJ); 737 return_rec(CX_JSON_NO_ERROR); 738 } else if (state == JP_STATE_OBJ_SEP_OR_CLOSE) { 739 // expect ',' or '}' 740 if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) { 741 json_add_state(json, JP_STATE_OBJ_NAME); 742 return_rec(CX_JSON_NO_ERROR); 743 } else if (token.tokentype == CX_JSON_TOKEN_END_OBJECT) { 744 // discard the obj from the value buffer 745 json->vbuf_size--; 746 return_rec(CX_JSON_NO_ERROR); 747 } else { 748 return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 749 } 750 } else { 751 // should be unreachable 752 assert(false); 753 return_rec(-1); // LCOV_EXCL_LINE 754 } 755 } 756 757 CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) { 758 // initialize output value 759 *value = &cx_json_value_nothing; 760 761 // check if the buffer has been filled 762 if (json->buffer.space == NULL) { 763 return CX_JSON_NULL_DATA; 764 } 765 766 // parse data 767 CxJsonStatus result; 768 do { 769 result = json_parse(json); 770 if (result == CX_JSON_NO_ERROR && json->states_size == 1) { 771 // final state reached 772 assert(json->states[0] == JP_STATE_VALUE_END); 773 assert(json->vbuf_size == 0); 774 775 // write output value 776 *value = json->parsed; 777 json->parsed = NULL; 778 779 // re-initialize state machine 780 json->states[0] = JP_STATE_VALUE_BEGIN; 781 782 return CX_JSON_NO_ERROR; 783 } 784 } while (result == CX_JSON_NO_ERROR); 785 786 // the parser might think there is no data 787 // but when we did not reach the final state, 788 // we know that there must be more to come 789 if (result == CX_JSON_NO_DATA && json->states_size > 1) { 790 return CX_JSON_INCOMPLETE_DATA; 791 } 792 793 return result; 794 } 795 796 CxJsonStatus cx_json_from_string(const CxAllocator *allocator, 797 cxstring str, CxJsonValue **value) { 798 *value = &cx_json_value_nothing; 799 CxJson parser; 800 cxJsonInit(&parser, allocator); 801 if (cxJsonFill(&parser, str)) { 802 // LCOV_EXCL_START 803 cxJsonDestroy(&parser); 804 return CX_JSON_BUFFER_ALLOC_FAILED; 805 // LCOV_EXCL_STOP 806 } 807 CxJsonStatus status = cxJsonNext(&parser, value); 808 // check if we consume the total string 809 CxJsonValue *chk_value = NULL; 810 CxJsonStatus chk_status = CX_JSON_NO_DATA; 811 if (status == CX_JSON_NO_ERROR) { 812 chk_status = cxJsonNext(&parser, &chk_value); 813 } 814 cxJsonDestroy(&parser); 815 if (chk_status == CX_JSON_NO_DATA) { 816 return status; 817 } else { 818 cxJsonValueFree(*value); 819 // if chk_value is nothing, the free is harmless 820 cxJsonValueFree(chk_value); 821 *value = &cx_json_value_nothing; 822 return CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN; 823 } 824 825 } 826 827 void cxJsonValueFree(CxJsonValue *value) { 828 if (value == NULL || value->type == CX_JSON_NOTHING) return; 829 switch (value->type) { 830 case CX_JSON_OBJECT: { 831 json_free_object_map(value->object); 832 break; 833 } 834 case CX_JSON_ARRAY: { 835 CxJsonArray array = value->array; 836 for (size_t i = 0; i < array.data_size; i++) { 837 cxJsonValueFree(array.data[i]); 838 } 839 cxFree(value->allocator, array.data); 840 break; 841 } 842 case CX_JSON_STRING: { 843 cxFree(value->allocator, value->string.ptr); 844 break; 845 } 846 default: { 847 break; 848 } 849 } 850 cxFree(value->allocator, value); 851 } 852 853 CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator) { 854 if (allocator == NULL) allocator = cxDefaultAllocator; 855 CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); 856 if (v == NULL) return NULL; 857 v->allocator = allocator; 858 v->type = CX_JSON_OBJECT; 859 v->object = json_create_object_map(allocator); 860 if (v->object == NULL) { // LCOV_EXCL_START 861 cxFree(allocator, v); 862 return NULL; 863 // LCOV_EXCL_STOP 864 } 865 return v; 866 } 867 868 CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator) { 869 if (allocator == NULL) allocator = cxDefaultAllocator; 870 CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); 871 if (v == NULL) return NULL; 872 v->allocator = allocator; 873 v->type = CX_JSON_ARRAY; 874 cx_array_initialize_a(allocator, v->array.data, 16); 875 if (v->array.data == NULL) { cxFree(allocator, v); return NULL; } 876 return v; 877 } 878 879 CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num) { 880 if (allocator == NULL) allocator = cxDefaultAllocator; 881 CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); 882 if (v == NULL) return NULL; 883 v->allocator = allocator; 884 v->type = CX_JSON_NUMBER; 885 v->number = num; 886 return v; 887 } 888 889 CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num) { 890 if (allocator == NULL) allocator = cxDefaultAllocator; 891 CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); 892 if (v == NULL) return NULL; 893 v->allocator = allocator; 894 v->type = CX_JSON_INTEGER; 895 v->integer = num; 896 return v; 897 } 898 899 CxJsonValue* cx_json_create_string(const CxAllocator* allocator, cxstring str) { 900 if (allocator == NULL) allocator = cxDefaultAllocator; 901 CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); 902 if (v == NULL) return NULL; 903 v->allocator = allocator; 904 v->type = CX_JSON_STRING; 905 cxmutstr s = cx_strdup_a(allocator, str); 906 if (s.ptr == NULL) { cxFree(allocator, v); return NULL; } 907 v->string = s; 908 return v; 909 } 910 911 CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit) { 912 if (allocator == NULL) allocator = cxDefaultAllocator; 913 CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); 914 if (v == NULL) return NULL; 915 v->allocator = allocator; 916 v->type = CX_JSON_LITERAL; 917 v->literal = lit; 918 return v; 919 } 920 921 // LCOV_EXCL_START 922 // never called as long as malloc() does not return NULL 923 static void json_arr_free_temp(CxJsonValue** values, size_t count) { 924 for (size_t i = 0; i < count; i++) { 925 if (values[i] == NULL) break; 926 cxJsonValueFree(values[i]); 927 } 928 cxFreeDefault(values); 929 } 930 // LCOV_EXCL_STOP 931 932 int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count) { 933 CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*)); 934 if (values == NULL) return -1; 935 for (size_t i = 0; i < count; i++) { 936 values[i] = cxJsonCreateNumber(arr->allocator, num[i]); 937 if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } 938 } 939 int ret = cxJsonArrAddValues(arr, values, count); 940 cxFreeDefault(values); 941 return ret; 942 } 943 944 int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count) { 945 CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*)); 946 if (values == NULL) return -1; 947 for (size_t i = 0; i < count; i++) { 948 values[i] = cxJsonCreateInteger(arr->allocator, num[i]); 949 if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } 950 } 951 int ret = cxJsonArrAddValues(arr, values, count); 952 cxFreeDefault(values); 953 return ret; 954 } 955 956 int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count) { 957 CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*)); 958 if (values == NULL) return -1; 959 for (size_t i = 0; i < count; i++) { 960 values[i] = cxJsonCreateString(arr->allocator, str[i]); 961 if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } 962 } 963 int ret = cxJsonArrAddValues(arr, values, count); 964 cxFreeDefault(values); 965 return ret; 966 } 967 968 int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count) { 969 CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*)); 970 if (values == NULL) return -1; 971 for (size_t i = 0; i < count; i++) { 972 values[i] = cxJsonCreateString(arr->allocator, str[i]); 973 if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } 974 } 975 int ret = cxJsonArrAddValues(arr, values, count); 976 cxFreeDefault(values); 977 return ret; 978 } 979 980 int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count) { 981 CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*)); 982 if (values == NULL) return -1; 983 for (size_t i = 0; i < count; i++) { 984 values[i] = cxJsonCreateLiteral(arr->allocator, lit[i]); 985 if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } 986 } 987 int ret = cxJsonArrAddValues(arr, values, count); 988 cxFreeDefault(values); 989 return ret; 990 } 991 992 int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count) { 993 CxArrayReallocator value_realloc = cx_array_reallocator(arr->allocator, NULL); 994 assert(arr->type == CX_JSON_ARRAY); 995 return cx_array_simple_copy_a(&value_realloc, 996 arr->array.data, 997 arr->array.data_size, 998 val, count 999 ); 1000 } 1001 1002 int cx_json_obj_put(CxJsonValue* obj, cxstring name, CxJsonValue* child) { 1003 return cxMapPut(obj->object, name, child); 1004 } 1005 1006 CxJsonValue* cx_json_obj_put_obj(CxJsonValue* obj, cxstring name) { 1007 CxJsonValue* v = cxJsonCreateObj(obj->allocator); 1008 if (v == NULL) return NULL; 1009 if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } 1010 return v; 1011 } 1012 1013 CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name) { 1014 CxJsonValue* v = cxJsonCreateArr(obj->allocator); 1015 if (v == NULL) return NULL; 1016 if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } 1017 return v; 1018 } 1019 1020 CxJsonValue* cx_json_obj_put_number(CxJsonValue* obj, cxstring name, double num) { 1021 CxJsonValue* v = cxJsonCreateNumber(obj->allocator, num); 1022 if (v == NULL) return NULL; 1023 if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } 1024 return v; 1025 } 1026 1027 CxJsonValue* cx_json_obj_put_integer(CxJsonValue* obj, cxstring name, int64_t num) { 1028 CxJsonValue* v = cxJsonCreateInteger(obj->allocator, num); 1029 if (v == NULL) return NULL; 1030 if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } 1031 return v; 1032 } 1033 1034 CxJsonValue* cx_json_obj_put_string(CxJsonValue* obj, cxstring name, cxstring str) { 1035 CxJsonValue* v = cxJsonCreateString(obj->allocator, str); 1036 if (v == NULL) return NULL; 1037 if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } 1038 return v; 1039 } 1040 1041 CxJsonValue* cx_json_obj_put_literal(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) { 1042 CxJsonValue* v = cxJsonCreateLiteral(obj->allocator, lit); 1043 if (v == NULL) return NULL; 1044 if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL;} 1045 return v; 1046 } 1047 1048 CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index) { 1049 if (index >= value->array.data_size) { 1050 return &cx_json_value_nothing; 1051 } 1052 return value->array.data[index]; 1053 } 1054 1055 CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index) { 1056 if (index >= value->array.data_size) { 1057 return NULL; 1058 } 1059 CxJsonValue *ret = value->array.data[index]; 1060 // TODO: replace with a low level cx_array_remove() 1061 size_t count = value->array.data_size - index - 1; 1062 if (count > 0) { 1063 memmove(value->array.data + index, value->array.data + index + 1, count * sizeof(CxJsonValue*)); 1064 } 1065 value->array.data_size--; 1066 return ret; 1067 } 1068 1069 char *cxJsonAsString(const CxJsonValue *value) { 1070 return value->string.ptr; 1071 } 1072 1073 cxstring cxJsonAsCxString(const CxJsonValue *value) { 1074 return cx_strcast(value->string); 1075 } 1076 1077 cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { 1078 return value->string; 1079 } 1080 1081 double cxJsonAsDouble(const CxJsonValue *value) { 1082 if (value->type == CX_JSON_INTEGER) { 1083 return (double) value->integer; 1084 } else { 1085 return value->number; 1086 } 1087 } 1088 1089 int64_t cxJsonAsInteger(const CxJsonValue *value) { 1090 if (value->type == CX_JSON_INTEGER) { 1091 return value->integer; 1092 } else { 1093 return (int64_t) value->number; 1094 } 1095 } 1096 1097 CxIterator cxJsonArrIter(const CxJsonValue *value) { 1098 return cxIteratorPtr( 1099 value->array.data, 1100 value->array.data_size, 1101 true // arrays need to keep order 1102 ); 1103 } 1104 1105 CxMapIterator cxJsonObjIter(const CxJsonValue *value) { 1106 return cxMapIterator(value->object); 1107 } 1108 1109 CxJsonValue *cx_json_obj_get(const CxJsonValue *value, cxstring name) { 1110 CxJsonValue *v = cxMapGet(value->object, name); 1111 if (v == NULL) { 1112 return &cx_json_value_nothing; 1113 } else { 1114 return v; 1115 } 1116 } 1117 1118 CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name) { 1119 CxJsonValue *v = NULL; 1120 cxMapRemoveAndGet(value->object, name, &v); 1121 return v; 1122 } 1123 1124 CxJsonWriter cxJsonWriterCompact(void) { 1125 return (CxJsonWriter) { 1126 false, 1127 6, 1128 false, 1129 4, 1130 false 1131 }; 1132 } 1133 1134 CxJsonWriter cxJsonWriterPretty(bool use_spaces) { 1135 return (CxJsonWriter) { 1136 true, 1137 6, 1138 use_spaces, 1139 4, 1140 false 1141 }; 1142 } 1143 1144 static int cx_json_writer_indent( 1145 void *target, 1146 cx_write_func wfunc, 1147 const CxJsonWriter *settings, 1148 unsigned int depth 1149 ) { 1150 if (depth == 0) return 0; 1151 1152 // determine the width and characters to use 1153 const char* indent; // for 32 prepared chars 1154 size_t width = depth; 1155 if (settings->indent_space) { 1156 if (settings->indent == 0) return 0; 1157 width *= settings->indent; 1158 indent = " "; 1159 } else { 1160 indent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; 1161 } 1162 1163 // calculate the number of write calls and write 1164 size_t full = width / 32; 1165 size_t remaining = width % 32; 1166 for (size_t i = 0; i < full; i++) { 1167 if (32 != wfunc(indent, 1, 32, target)) return 1; 1168 } 1169 if (remaining != wfunc(indent, 1, remaining, target)) return 1; 1170 1171 return 0; 1172 } 1173 1174 1175 int cx_json_write_rec( 1176 void *target, 1177 const CxJsonValue *value, 1178 cx_write_func wfunc, 1179 const CxJsonWriter *settings, 1180 unsigned int depth 1181 ) { 1182 // keep track of written items 1183 // the idea is to reduce the number of jumps for error checking 1184 size_t actual = 0, expected = 0; 1185 1186 // small buffer for number to string conversions 1187 char numbuf[40]; 1188 1189 // recursively write the values 1190 switch (value->type) { 1191 case CX_JSON_OBJECT: { 1192 const char *begin_obj = "{\n"; 1193 if (settings->pretty) { 1194 actual += wfunc(begin_obj, 1, 2, target); 1195 expected += 2; 1196 } else { 1197 actual += wfunc(begin_obj, 1, 1, target); 1198 expected++; 1199 } 1200 depth++; 1201 CxMapIterator member_iter = cxJsonObjIter(value); 1202 cx_foreach(const CxMapEntry *, member, member_iter) { 1203 // possible indentation 1204 if (settings->pretty) { 1205 if (cx_json_writer_indent(target, wfunc, settings, depth)) { 1206 return 1; // LCOV_EXCL_LINE 1207 } 1208 } 1209 1210 // the name 1211 actual += wfunc("\"", 1, 1, target); 1212 cxstring key = cx_strn(member->key->data, member->key->len); 1213 cxmutstr name = escape_string(key, settings->escape_slash); 1214 actual += wfunc(name.ptr, 1, name.length, target); 1215 actual += wfunc("\"", 1, 1, target); 1216 const char *obj_name_sep = ": "; 1217 if (settings->pretty) { 1218 actual += wfunc(obj_name_sep, 1, 2, target); 1219 expected += 4 + name.length; 1220 } else { 1221 actual += wfunc(obj_name_sep, 1, 1, target); 1222 expected += 3 + name.length; 1223 } 1224 if (name.ptr != key.ptr) { 1225 cx_strfree(&name); 1226 } 1227 1228 // the value 1229 if (cx_json_write_rec(target, member->value, wfunc, settings, depth)) return 1; 1230 1231 // end of object-value 1232 if (member_iter.index < member_iter.elem_count - 1) { 1233 const char *obj_value_sep = ",\n"; 1234 if (settings->pretty) { 1235 actual += wfunc(obj_value_sep, 1, 2, target); 1236 expected += 2; 1237 } else { 1238 actual += wfunc(obj_value_sep, 1, 1, target); 1239 expected++; 1240 } 1241 } else { 1242 if (settings->pretty) { 1243 actual += wfunc("\n", 1, 1, target); 1244 expected ++; 1245 } 1246 } 1247 } 1248 depth--; 1249 if (settings->pretty) { 1250 if (cx_json_writer_indent(target, wfunc, settings, depth)) return 1; 1251 } 1252 actual += wfunc("}", 1, 1, target); 1253 expected++; 1254 break; 1255 } 1256 case CX_JSON_ARRAY: { 1257 actual += wfunc("[", 1, 1, target); 1258 expected++; 1259 CxIterator iter = cxJsonArrIter(value); 1260 cx_foreach(CxJsonValue*, element, iter) { 1261 if (cx_json_write_rec( 1262 target, element, 1263 wfunc, settings, depth) 1264 ) { 1265 return 1; // LCOV_EXCL_LINE 1266 } 1267 1268 if (iter.index < iter.elem_count - 1) { 1269 const char *arr_value_sep = ", "; 1270 if (settings->pretty) { 1271 actual += wfunc(arr_value_sep, 1, 2, target); 1272 expected += 2; 1273 } else { 1274 actual += wfunc(arr_value_sep, 1, 1, target); 1275 expected++; 1276 } 1277 } 1278 } 1279 actual += wfunc("]", 1, 1, target); 1280 expected++; 1281 break; 1282 } 1283 case CX_JSON_STRING: { 1284 actual += wfunc("\"", 1, 1, target); 1285 cxmutstr str = escape_string(cx_strcast(value->string), 1286 settings->escape_slash); 1287 actual += wfunc(str.ptr, 1, str.length, target); 1288 actual += wfunc("\"", 1, 1, target); 1289 expected += 2 + str.length; 1290 if (str.ptr != value->string.ptr) { 1291 cx_strfree(&str); 1292 } 1293 break; 1294 } 1295 case CX_JSON_NUMBER: { 1296 int precision = settings->frac_max_digits; 1297 // because of the way how %g is defined, we need to 1298 // double the precision and truncate ourselves 1299 precision = 1 + (precision > 15 ? 30 : 2 * precision); 1300 snprintf(numbuf, 40, "%.*g", precision, value->number); 1301 char *dot, *exp; 1302 unsigned char max_digits; 1303 // find the decimal separator and hope that it's one of . or , 1304 dot = strchr(numbuf, '.'); 1305 if (dot == NULL) { 1306 dot = strchr(numbuf, ','); 1307 } 1308 if (dot == NULL) { 1309 // no decimal separator found 1310 // output everything until a possible exponent 1311 max_digits = 30; 1312 dot = numbuf; 1313 } else { 1314 // found a decimal separator 1315 // output everything until the separator 1316 // and set max digits to what the settings say 1317 size_t len = dot - numbuf; 1318 actual += wfunc(numbuf, 1, len, target); 1319 expected += len; 1320 max_digits = settings->frac_max_digits; 1321 if (max_digits > 15) { 1322 max_digits = 15; 1323 } 1324 // locale independent separator 1325 if (max_digits > 0) { 1326 actual += wfunc(".", 1, 1, target); 1327 expected++; 1328 } 1329 dot++; 1330 } 1331 // find the exponent 1332 exp = strchr(dot, 'e'); 1333 if (exp == NULL) { 1334 // no exponent - output the rest 1335 if (max_digits > 0) { 1336 size_t len = strlen(dot); 1337 if (len > max_digits) { 1338 len = max_digits; 1339 } 1340 actual += wfunc(dot, 1, len, target); 1341 expected += len; 1342 } 1343 } else { 1344 // exponent found - truncate the frac digits 1345 // and then output the rest 1346 if (max_digits > 0) { 1347 size_t len = exp - dot - 1; 1348 if (len > max_digits) { 1349 len = max_digits; 1350 } 1351 actual += wfunc(dot, 1, len, target); 1352 expected += len; 1353 } 1354 actual += wfunc("e", 1, 1, target); 1355 expected++; 1356 exp++; 1357 size_t len = strlen(exp); 1358 actual += wfunc(exp, 1, len, target); 1359 expected += len; 1360 } 1361 break; 1362 } 1363 case CX_JSON_INTEGER: { 1364 snprintf(numbuf, 32, "%" PRIi64, value->integer); 1365 size_t len = strlen(numbuf); 1366 actual += wfunc(numbuf, 1, len, target); 1367 expected += len; 1368 break; 1369 } 1370 case CX_JSON_LITERAL: { 1371 if (value->literal == CX_JSON_TRUE) { 1372 actual += wfunc("true", 1, 4, target); 1373 expected += 4; 1374 } else if (value->literal == CX_JSON_FALSE) { 1375 actual += wfunc("false", 1, 5, target); 1376 expected += 5; 1377 } else { 1378 actual += wfunc("null", 1, 4, target); 1379 expected += 4; 1380 } 1381 break; 1382 } 1383 case CX_JSON_NOTHING: { 1384 // deliberately supported as an empty string! 1385 // users might want to just write the result 1386 // of a get operation without testing the value 1387 // and therefore this should not blow up 1388 break; 1389 } 1390 default: assert(false); // LCOV_EXCL_LINE 1391 } 1392 1393 return expected != actual; 1394 } 1395 1396 int cxJsonWrite( 1397 void *target, 1398 const CxJsonValue *value, 1399 cx_write_func wfunc, 1400 const CxJsonWriter *settings 1401 ) { 1402 assert(target != NULL); 1403 assert(value != NULL); 1404 assert(wfunc != NULL); 1405 1406 CxJsonWriter writer_default = cxJsonWriterCompact(); 1407 if (settings == NULL) { 1408 settings = &writer_default; 1409 } 1410 return cx_json_write_rec(target, value, wfunc, settings, 0); 1411 } 1412