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 "util_allocator.h" 30 #include "cx/test.h" 31 32 #include "cx/json.h" 33 #include "cx/compare.h" 34 35 CX_TEST(test_json_init_default) { 36 CxJson json; 37 CX_TEST_DO { 38 cxJsonInit(&json, NULL); 39 CX_TEST_ASSERT(json.states == json.states_internal); 40 CX_TEST_ASSERT(json.states_size == 1); 41 CX_TEST_ASSERT(json.states_capacity >= 8); 42 CX_TEST_ASSERT(json.vbuf == json.vbuf_internal); 43 CX_TEST_ASSERT(json.vbuf_size == 0); 44 CX_TEST_ASSERT(json.vbuf_capacity >= 8); 45 cxJsonDestroy(&json); 46 } 47 } 48 49 CX_TEST(test_json_simple_object) { 50 cxstring text = cx_str( 51 "{\n" 52 "\t\"message\":\"success\",\n" 53 "\t\"position\":{\n" 54 "\t\t\"longitude\":-94.7099,\n" 55 "\t\t\"latitude\":51.5539\n" 56 "\t},\n" 57 "\t\"timestamp\":1729348561,\n" 58 "\t\"alive\":true\n" 59 "}" 60 ); 61 62 CX_TEST_DO { 63 CxJsonStatus result; 64 65 CxJson json; 66 cxJsonInit(&json, NULL); 67 cxJsonFill(&json, text); 68 69 // parse the big fat object 70 CxJsonValue *obj; 71 result = cxJsonNext(&json, &obj); 72 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 73 74 // check the contents 75 CX_TEST_ASSERT(cxJsonIsObject(obj)); 76 77 CxJsonValue *message = cxJsonObjGet(obj, "message"); 78 CX_TEST_ASSERT(cxJsonIsString(message)); 79 CX_TEST_ASSERT(0 == cx_strcmp( 80 cxJsonAsCxString(message), 81 "success") 82 ); 83 84 CxJsonValue *position = cxJsonObjGet(obj, "position"); 85 CX_TEST_ASSERT(cxJsonIsObject(position)); 86 CxJsonValue *longitude = cxJsonObjGet(position, "longitude"); 87 CX_TEST_ASSERT(cxJsonIsNumber(longitude)); 88 CX_TEST_ASSERT(!cxJsonIsInteger(longitude)); 89 CX_TEST_ASSERT(0 == cx_vcmp_double(cxJsonAsDouble(longitude), -94.7099)); 90 CX_TEST_ASSERT(cxJsonAsInteger(longitude) == -94); 91 CxJsonValue *latitude = cxJsonObjGet(position, "latitude"); 92 CX_TEST_ASSERT(cxJsonIsNumber(latitude)); 93 CX_TEST_ASSERT(!cxJsonIsInteger(latitude)); 94 CX_TEST_ASSERT(0 == cx_vcmp_double(cxJsonAsDouble(latitude), 51.5539)); 95 CX_TEST_ASSERT(cxJsonAsInteger(latitude) == 51); 96 97 CxJsonValue *timestamp = cxJsonObjGet(obj, "timestamp"); 98 CX_TEST_ASSERT(cxJsonIsInteger(timestamp)); 99 CX_TEST_ASSERT(cxJsonIsNumber(timestamp)); 100 CX_TEST_ASSERT(cxJsonAsInteger(timestamp) == 1729348561); 101 CX_TEST_ASSERT(cxJsonAsDouble(timestamp) == 1729348561.0); 102 103 CxJsonValue *alive = cxJsonObjGet(obj, "alive"); 104 CX_TEST_ASSERT(cxJsonIsBool(alive)); 105 CX_TEST_ASSERT(cxJsonIsTrue(alive)); 106 CX_TEST_ASSERT(!cxJsonIsFalse(alive)); 107 CX_TEST_ASSERT(cxJsonAsBool(alive)); 108 109 // this recursively frees everything else 110 cxJsonValueFree(obj); 111 112 // we only have one object that already contained all the data 113 result = cxJsonNext(&json, &obj); 114 CX_TEST_ASSERT(result == CX_JSON_NO_DATA); 115 116 cxJsonDestroy(&json); 117 } 118 } 119 120 CX_TEST(test_json_large_object) { 121 CxJsonValue *obj = cxJsonCreateObj(NULL); 122 CX_TEST_DO { 123 cxJsonObjPutString(obj, "mystring", "test"); 124 char buf[10]; 125 for (unsigned i = 0 ; i < 300 ; i++) { 126 sprintf(buf, "key %d", i); 127 cxJsonObjPutInteger(obj, buf, i); 128 } 129 CX_TEST_ASSERT(301 == cxJsonObjSize(obj)); 130 // some samples 131 CxJsonValue *v; 132 v = cxJsonObjGet(obj, "key 64"); 133 CX_TEST_ASSERT(cxJsonIsInteger(v)); 134 CX_TEST_ASSERT(cxJsonAsInteger(v) == 64); 135 v = cxJsonObjGet(obj, "key 228"); 136 CX_TEST_ASSERT(cxJsonIsInteger(v)); 137 CX_TEST_ASSERT(cxJsonAsInteger(v) == 228); 138 139 v = cxJsonObjGet(obj, "mystring"); 140 CX_TEST_ASSERT(cxJsonIsString(v)); 141 CX_TEST_ASSERT(0 == cx_strcmp(cxJsonAsCxMutStr(v), "test")); 142 } 143 cxJsonValueFree(obj); 144 } 145 146 CX_TEST(test_json_from_string) { 147 cxstring text = cx_str( 148 "{\n" 149 "\t\"message\":\"success\",\n" 150 "\t\"position\":{\n" 151 "\t\t\"longitude\":-94.7099,\n" 152 "\t\t\"latitude\":51.5539\n" 153 "\t},\n" 154 "\t\"timestamp\":1729348561,\n" 155 "\t\"alive\":true\n" 156 "}" 157 ); 158 159 CX_TEST_DO { 160 CxJsonValue *obj; 161 CX_TEST_ASSERT(cxJsonFromString(NULL, text, &obj) == CX_JSON_NO_ERROR); 162 163 // check the contents 164 CX_TEST_ASSERT(cxJsonIsObject(obj)); 165 166 CxJsonValue *message = cxJsonObjGet(obj, "message"); 167 CX_TEST_ASSERT(cxJsonIsString(message)); 168 CX_TEST_ASSERT(0 == cx_strcmp( 169 cxJsonAsCxString(message), 170 "success") 171 ); 172 173 CxJsonValue *position = cxJsonObjGet(obj, "position"); 174 CX_TEST_ASSERT(cxJsonIsObject(position)); 175 CxJsonValue *longitude = cxJsonObjGet(position, "longitude"); 176 CX_TEST_ASSERT(cxJsonIsNumber(longitude)); 177 CX_TEST_ASSERT(!cxJsonIsInteger(longitude)); 178 CX_TEST_ASSERT(0 == cx_vcmp_double(cxJsonAsDouble(longitude), -94.7099)); 179 CX_TEST_ASSERT(cxJsonAsInteger(longitude) == -94); 180 CxJsonValue *latitude = cxJsonObjGet(position, "latitude"); 181 CX_TEST_ASSERT(cxJsonIsNumber(latitude)); 182 CX_TEST_ASSERT(!cxJsonIsInteger(latitude)); 183 CX_TEST_ASSERT(0 == cx_vcmp_double(cxJsonAsDouble(latitude), 51.5539)); 184 CX_TEST_ASSERT(cxJsonAsInteger(latitude) == 51); 185 186 CxJsonValue *timestamp = cxJsonObjGet(obj, "timestamp"); 187 CX_TEST_ASSERT(cxJsonIsInteger(timestamp)); 188 CX_TEST_ASSERT(cxJsonIsNumber(timestamp)); 189 CX_TEST_ASSERT(cxJsonAsInteger(timestamp) == 1729348561); 190 CX_TEST_ASSERT(cxJsonAsDouble(timestamp) == 1729348561.0); 191 192 CxJsonValue *alive = cxJsonObjGet(obj, "alive"); 193 CX_TEST_ASSERT(cxJsonIsBool(alive)); 194 CX_TEST_ASSERT(cxJsonIsTrue(alive)); 195 CX_TEST_ASSERT(!cxJsonIsFalse(alive)); 196 CX_TEST_ASSERT(cxJsonAsBool(alive)); 197 198 cxJsonValueFree(obj); 199 } 200 } 201 202 CX_TEST(test_json_from_string_errors) { 203 CX_TEST_DO { 204 CxJsonValue *obj = NULL; 205 CX_TEST_ASSERT(cxJsonFromString(NULL, "", &obj) == CX_JSON_NO_DATA); 206 CX_TEST_ASSERT(cxJsonFromString(NULL, cx_str(NULL), &obj) == CX_JSON_NO_DATA); 207 CX_TEST_ASSERT(cxJsonFromString(NULL, "\"incomplete", &obj) == CX_JSON_INCOMPLETE_DATA); 208 CX_TEST_ASSERT(cxJsonFromString(NULL, "{ \"incomplete\": ", &obj) == CX_JSON_INCOMPLETE_DATA); 209 CX_TEST_ASSERT(cxJsonFromString(NULL, "{\"number\": 47110815!}", &obj) == CX_JSON_FORMAT_ERROR_NUMBER); 210 CX_TEST_ASSERT(cxJsonFromString(NULL, "[ \"unexpected token\" : true ]", &obj) == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 211 CX_TEST_ASSERT(cxJsonFromString(NULL, "} [", &obj) == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 212 CX_TEST_ASSERT(obj && obj->type == CX_JSON_NOTHING); 213 } 214 } 215 216 CX_TEST(test_json_from_string_multiple_values) { 217 CxJsonStatus status; 218 CxJsonValue *obj; 219 CX_TEST_DO { 220 obj = NULL; 221 status = cxJsonFromString(NULL, "{ \"obj1\": \"hello\" }\n\"value2\"\n", &obj); 222 CX_TEST_ASSERT(obj != NULL); 223 CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); 224 CX_TEST_ASSERT(status == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 225 226 obj = NULL; 227 status = cxJsonFromString(NULL, "\"value\" \n ] syntax error [", &obj); 228 CX_TEST_ASSERT(obj != NULL); 229 CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); 230 CX_TEST_ASSERT(status == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 231 } 232 } 233 234 CX_TEST(test_json_from_string_untrimmed) { 235 CxJsonStatus status; 236 CxJsonValue *obj; 237 CX_TEST_DO { 238 obj = NULL; 239 status = cxJsonFromString(NULL, "\n\t{ \"obj1\": \"hello\" } \n", &obj); 240 CX_TEST_ASSERT(status == CX_JSON_NO_ERROR); 241 CX_TEST_ASSERT(cxJsonIsObject(obj)); 242 CxJsonValue *obj1 = cxJsonObjGet(obj, "obj1"); 243 CX_TEST_ASSERT(cxJsonIsString(obj1)); 244 CX_TEST_ASSERT(cx_strcmp(cxJsonAsCxString(obj1), "hello") == 0); 245 246 cxJsonValueFree(obj); 247 } 248 } 249 250 CX_TEST(test_json_escaped_strings) { 251 cxstring text = cx_str( 252 "{\n" 253 "\t\"object\":\"{\\n\\t\\\"object\\\":null\\n}\",\n" 254 "\t\"ctrl-chars\":\"\\\\foo\\r\\nbar\\f*ring\\/ring*\\b\"\n" 255 "}" 256 ); 257 258 CxJson json; 259 cxJsonInit(&json, NULL); 260 CX_TEST_DO { 261 cxJsonFill(&json, text); 262 CxJsonValue *obj; 263 CxJsonStatus result = cxJsonNext(&json, &obj); 264 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 265 CX_TEST_ASSERT(cxJsonIsObject(obj)); 266 CxJsonValue *object = cxJsonObjGet(obj, "object"); 267 CX_TEST_ASSERT(cxJsonIsString(object)); 268 CX_TEST_ASSERT(0 == cx_strcmp( 269 cxJsonAsCxString(object), 270 "{\n\t\"object\":null\n}") 271 ); 272 CxJsonValue *ctrl = cxJsonObjGet(obj, "ctrl-chars"); 273 CX_TEST_ASSERT(cxJsonIsString(ctrl)); 274 CX_TEST_ASSERT(0 == cx_strcmp( 275 cxJsonAsCxString(ctrl), 276 "\\foo\r\nbar\f*ring/ring*\b") 277 ); 278 cxJsonValueFree(obj); 279 } 280 cxJsonDestroy(&json); 281 } 282 283 CX_TEST(test_json_escaped_unicode_strings) { 284 cxstring text = cx_str( 285 "{\n" 286 "\"ascii\":\"\\u0041\\u0053\\u0043\\u0049\\u0049\",\n" 287 "\"unicode\":\"\\u00df\\u00DF\",\n" 288 "\"mixed\":\"mixed ä ö \\u00e4 \\u00f6\",\n" 289 "\"wide\":\"\\u03a3\\u29b0\",\n" 290 "\"surrogatepair1\":\"\\ud83e\\udff5\",\n" 291 "\"surrogatepair2\":\"test\\ud83e\\udff1AA\"\n," 292 "\"mixed2\":\"123\\u03a3\\ud83e\\udfc5\\u00df\"" 293 "}" 294 ); 295 296 CxJson json; 297 cxJsonInit(&json, NULL); 298 CX_TEST_DO { 299 cxJsonFill(&json, text); 300 CxJsonValue *obj; 301 CxJsonStatus result = cxJsonNext(&json, &obj); 302 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 303 CX_TEST_ASSERT(cxJsonIsObject(obj)); 304 305 CxJsonValue *ascii = cxJsonObjGet(obj, "ascii"); 306 CX_TEST_ASSERT(cxJsonIsString(ascii)); 307 CX_TEST_ASSERT(0 == cx_strcmp( 308 cxJsonAsCxString(ascii), 309 "ASCII") 310 ); 311 312 CxJsonValue *unicode = cxJsonObjGet(obj, "unicode"); 313 CX_TEST_ASSERT(cxJsonIsString(unicode)); 314 CX_TEST_ASSERT(0 == cx_strcmp( 315 cxJsonAsCxString(unicode), 316 "ßß") 317 ); 318 319 CxJsonValue *mixed = cxJsonObjGet(obj, "mixed"); 320 CX_TEST_ASSERT(cxJsonIsString(mixed)); 321 CX_TEST_ASSERT(0 == cx_strcmp( 322 cxJsonAsCxString(mixed), 323 "mixed ä ö ä ö") 324 ); 325 326 CxJsonValue *wide = cxJsonObjGet(obj, "wide"); 327 CX_TEST_ASSERT(cxJsonIsString(wide)); 328 CX_TEST_ASSERT(0 == cx_strcmp(cxJsonAsCxString(wide), "Σ⦰")); 329 330 CxJsonValue *surrogatepair1 = cxJsonObjGet(obj, "surrogatepair1"); 331 CX_TEST_ASSERT(cxJsonIsString(surrogatepair1)); 332 CX_TEST_ASSERT(0 == cx_strcmp( 333 cxJsonAsCxString(surrogatepair1), 334 "\xf0\x9f\xaf\xb5") 335 ); 336 337 CxJsonValue *surrogatepair2 = cxJsonObjGet(obj, "surrogatepair2"); 338 CX_TEST_ASSERT(cxJsonIsString(surrogatepair2)); 339 CX_TEST_ASSERT(0 == cx_strcmp( 340 cxJsonAsCxString(surrogatepair2), 341 "test\xf0\x9f\xaf\xb1" "AA") 342 ); 343 344 CxJsonValue *mixed2 = cxJsonObjGet(obj, "mixed2"); 345 char test[16]; 346 strncpy(test, mixed2->string.ptr, 15); 347 CX_TEST_ASSERT(cxJsonIsString(mixed2)); 348 CX_TEST_ASSERT(0 == cx_strcmp( 349 cxJsonAsCxString(mixed2), 350 "123\xce\xa3\xf0\x9f\xaf\x85ß") 351 ); 352 353 cxJsonValueFree(obj); 354 } 355 cxJsonDestroy(&json); 356 } 357 358 CX_TEST(test_json_escaped_unicode_malformed) { 359 CxJson json; 360 cxJsonInit(&json, NULL); 361 CxJsonValue *obj; 362 CxJsonStatus result; 363 CX_TEST_DO { 364 cxJsonFill(&json, "\"too few digits \\u123\""); 365 result = cxJsonNext(&json, &obj); 366 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 367 CX_TEST_ASSERT(cxJsonIsString(obj)); 368 CX_TEST_ASSERT(0 == cx_strcmp( 369 cxJsonAsCxString(obj), 370 "too few digits \\u123" 371 )); 372 cxJsonValueFree(obj); 373 cxJsonFill(&json, "\"too many digits \\u00E456\""); 374 result = cxJsonNext(&json, &obj); 375 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 376 CX_TEST_ASSERT(cxJsonIsString(obj)); 377 CX_TEST_ASSERT(0 == cx_strcmp( 378 cxJsonAsCxString(obj), 379 "too many digits ä56" 380 )); 381 cxJsonValueFree(obj); 382 cxJsonFill(&json, "\"only high \\uD800 surrogate\""); 383 result = cxJsonNext(&json, &obj); 384 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 385 CX_TEST_ASSERT(cxJsonIsString(obj)); 386 CX_TEST_ASSERT(0 == cx_strcmp( 387 cxJsonAsCxString(obj), 388 "only high \\uD800 surrogate" 389 )); 390 cxJsonValueFree(obj); 391 cxJsonFill(&json, "\"only low \\uDC00 surrogate\""); 392 result = cxJsonNext(&json, &obj); 393 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 394 CX_TEST_ASSERT(cxJsonIsString(obj)); 395 CX_TEST_ASSERT(0 == cx_strcmp( 396 cxJsonAsCxString(obj), 397 "only low \\uDC00 surrogate" 398 )); 399 cxJsonValueFree(obj); 400 cxJsonFill(&json, "\"two high \\uD800\\uD800 surrogates\""); 401 result = cxJsonNext(&json, &obj); 402 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 403 CX_TEST_ASSERT(cxJsonIsString(obj)); 404 CX_TEST_ASSERT(0 == cx_strcmp( 405 cxJsonAsCxString(obj), 406 "two high \\uD800\\uD800 surrogates" 407 )); 408 cxJsonValueFree(obj); 409 cxJsonFill(&json, "\"high plus bullshit \\uD800\\u567 foo\""); 410 result = cxJsonNext(&json, &obj); 411 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 412 CX_TEST_ASSERT(cxJsonIsString(obj)); 413 CX_TEST_ASSERT(0 == cx_strcmp( 414 cxJsonAsCxString(obj), 415 "high plus bullshit \\uD800\\u567 foo" 416 )); 417 cxJsonValueFree(obj); 418 } 419 cxJsonDestroy(&json); 420 } 421 422 CX_TEST(test_json_escaped_end_of_string) { 423 CxJson json; 424 cxJsonInit(&json, NULL); 425 CX_TEST_DO { 426 // first test, normal scenario 427 cxJsonFill(&json, "\"a \\\"test\\\" string\""); 428 CxJsonValue *val; 429 CxJsonStatus result = cxJsonNext(&json, &val); 430 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 431 CX_TEST_ASSERT(cxJsonIsString(val)); 432 CX_TEST_ASSERT(0 == cx_strcmp( 433 cxJsonAsCxString(val), 434 "a \"test\" string") 435 ); 436 cxJsonValueFree(val); 437 438 // second test - uncompleted token with hanging escape char 439 cxJsonFill(&json, "\"a \\\"test\\"); 440 result = cxJsonNext(&json, &val); 441 CX_TEST_ASSERT(result == CX_JSON_INCOMPLETE_DATA); 442 cxJsonFill(&json, "\" string\""); 443 result = cxJsonNext(&json, &val); 444 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 445 CX_TEST_ASSERT(cxJsonIsString(val)); 446 CX_TEST_ASSERT(0 == cx_strcmp( 447 cxJsonAsCxString(val), 448 "a \"test\" string") 449 ); 450 cxJsonValueFree(val); 451 } 452 cxJsonDestroy(&json); 453 } 454 455 CX_TEST(test_json_object_incomplete_token) { 456 cxstring text = cx_str( 457 "{\"message\":\"success\" , \"__timestamp\":1729348561}"); 458 cxstring parts[16]; 459 size_t nparts = 0; // split the json text into mulple parts 460 for(size_t i=0;i<text.length;i+=4) { 461 parts[nparts++] = cx_strsubsl(text, i, 4); 462 } 463 464 CX_TEST_DO { 465 CxJsonStatus result; 466 467 CxJson json; 468 cxJsonInit(&json, NULL); 469 CxJsonValue *obj; 470 471 size_t part = 0; 472 while(part < nparts - 1) { 473 cxJsonFill(&json, parts[part]); 474 part++; 475 result = cxJsonNext(&json, &obj); 476 CX_TEST_ASSERT(result == CX_JSON_INCOMPLETE_DATA); 477 } 478 cxJsonFill(&json, parts[nparts - 1]); 479 result = cxJsonNext(&json, &obj); 480 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 481 CX_TEST_ASSERT(cxJsonIsObject(obj)); 482 483 CxJsonValue *message = cxJsonObjGet(obj, "message"); 484 CX_TEST_ASSERT(cxJsonIsString(message)); 485 CX_TEST_ASSERT(0 == cx_strcmp( 486 cxJsonAsCxString(message), 487 "success") 488 ); 489 CxJsonValue *timestamp = cxJsonObjGet(obj, "__timestamp"); 490 CX_TEST_ASSERT(message->type == CX_JSON_STRING); 491 CX_TEST_ASSERT(cxJsonIsInteger(timestamp)); 492 CX_TEST_ASSERT(cxJsonAsInteger(timestamp) == 1729348561); 493 494 // this recursively frees everything else 495 cxJsonValueFree(obj); 496 497 // now there is everything read 498 result = cxJsonNext(&json, &obj); 499 CX_TEST_ASSERT(result == CX_JSON_NO_DATA); 500 501 // Test 2: abort after incomplete token 502 cxJsonReset(&json); 503 504 cxJsonFill(&json, "\"incomplete token"); 505 result = cxJsonNext(&json, &obj); 506 CX_TEST_ASSERT(result == CX_JSON_INCOMPLETE_DATA); 507 508 cxJsonDestroy(&json); 509 } 510 } 511 512 CX_TEST(test_json_token_wrongly_completed) { 513 CxTestingAllocator talloc; 514 cx_testing_allocator_init(&talloc); 515 const CxAllocator *alloc = &talloc.base; 516 517 cxstring text = cx_str("{\"number\": 47110815!}"); 518 cxstring part1 = cx_strsubsl(text, 0, 16); 519 cxstring part2 = cx_strsubs(text, 16); 520 521 CX_TEST_DO { 522 CxJson json; 523 cxJsonInit(&json, alloc); 524 525 CxJsonStatus result; 526 CxJsonValue *obj; 527 528 cxJsonFill(&json, part1); 529 result = cxJsonNext(&json, &obj); 530 CX_TEST_ASSERT(result == CX_JSON_INCOMPLETE_DATA); 531 cxJsonFill(&json, part2); 532 result = cxJsonNext(&json, &obj); 533 CX_TEST_ASSERT(result == CX_JSON_FORMAT_ERROR_NUMBER); 534 CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); 535 536 cxJsonDestroy(&json); 537 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 538 } 539 cx_testing_allocator_destroy(&talloc); 540 } 541 542 CX_TEST(test_json_parenthesis_mismatch) { 543 CxTestingAllocator talloc; 544 cx_testing_allocator_init(&talloc); 545 const CxAllocator *alloc = &talloc.base; 546 547 CX_TEST_DO { 548 CxJson json; 549 cxJsonInit(&json, alloc); 550 551 CxJsonStatus result; 552 CxJsonValue *obj; 553 554 cxJsonFill(&json, "[0, 1, 2, 3}"); 555 result = cxJsonNext(&json, &obj); 556 CX_TEST_ASSERT(result == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 557 558 cxJsonReset(&json); 559 cxJsonFill(&json, "{\"test\": 42]"); 560 result = cxJsonNext(&json, &obj); 561 CX_TEST_ASSERT(result == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 562 563 cxJsonDestroy(&json); 564 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 565 } 566 cx_testing_allocator_destroy(&talloc); 567 } 568 569 CX_TEST(test_json_object_name_is_no_string) { 570 CxTestingAllocator talloc; 571 cx_testing_allocator_init(&talloc); 572 const CxAllocator *alloc = &talloc.base; 573 574 CX_TEST_DO { 575 CxJson json; 576 cxJsonInit(&json, alloc); 577 578 CxJsonStatus result; 579 CxJsonValue *obj; 580 581 cxJsonFill(&json, "{42: \"test\"}"); 582 result = cxJsonNext(&json, &obj); 583 CX_TEST_ASSERT(result == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 584 585 cxJsonDestroy(&json); 586 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 587 } 588 cx_testing_allocator_destroy(&talloc); 589 } 590 591 CX_TEST(test_json_subsequent_fill) { 592 cxstring text = cx_str( 593 "{\"message\":\"success\" , \"__timestamp\":1729348561}"); 594 595 cxstring part1 = cx_strsubsl(text, 0, 25); 596 cxstring part2 = cx_strsubs(text, 25); 597 598 CX_TEST_DO { 599 CxJson json; 600 cxJsonInit(&json, NULL); 601 CxJsonValue *obj; 602 603 cxJsonFill(&json, part1); 604 cxJsonFill(&json, part2); 605 CxJsonStatus result = cxJsonNext(&json, &obj); 606 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 607 CX_TEST_ASSERT(cxJsonIsObject(obj)); 608 609 CxJsonValue *message = cxJsonObjGet(obj, "message"); 610 CX_TEST_ASSERT(cxJsonIsString(message)); 611 CX_TEST_ASSERT(0 == cx_strcmp( 612 cxJsonAsCxString(message), 613 "success") 614 ); 615 CxJsonValue *timestamp = cxJsonObjGet(obj, "__timestamp"); 616 CX_TEST_ASSERT(message->type == CX_JSON_STRING); 617 CX_TEST_ASSERT(cxJsonIsInteger(timestamp)); 618 CX_TEST_ASSERT(cxJsonAsInteger(timestamp) == 1729348561); 619 620 cxJsonValueFree(obj); 621 result = cxJsonNext(&json, &obj); 622 CX_TEST_ASSERT(result == CX_JSON_NO_DATA); 623 624 cxJsonDestroy(&json); 625 } 626 } 627 628 CX_TEST(test_json_no_fill) { 629 CxJson json; 630 cxJsonInit(&json, NULL); 631 CX_TEST_DO { 632 CxJsonValue *obj = NULL; 633 CxJsonStatus result = cxJsonNext(&json, &obj); 634 CX_TEST_ASSERT(result == CX_JSON_NULL_DATA); 635 CX_TEST_ASSERT(obj != NULL); 636 CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); 637 } 638 cxJsonDestroy(&json); 639 } 640 641 CX_TEST(test_json_null_fill) { 642 CxJson json; 643 cxJsonInit(&json, NULL); 644 CX_TEST_DO { 645 CxJsonStatus result; 646 CxJsonValue *obj = NULL; 647 cxstring nullstr = cx_strn(NULL, 0); 648 cxJsonFill(&json, nullstr); 649 result = cxJsonNext(&json, &obj); 650 CX_TEST_ASSERT(result == CX_JSON_NO_DATA); 651 CX_TEST_ASSERT(obj != NULL); 652 CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); 653 obj = NULL; 654 655 cxJsonFill(&json, "[0, 1"); 656 result = cxJsonNext(&json, &obj); 657 CX_TEST_ASSERT(result == CX_JSON_INCOMPLETE_DATA); 658 CX_TEST_ASSERT(obj != NULL); 659 CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); 660 obj = NULL; 661 662 cxJsonFill(&json, nullstr); 663 result = cxJsonNext(&json, &obj); 664 CX_TEST_ASSERT(result == CX_JSON_INCOMPLETE_DATA); 665 CX_TEST_ASSERT(obj != NULL); 666 CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); 667 obj = NULL; 668 669 cxJsonFill(&json, ", 2]"); 670 result = cxJsonNext(&json, &obj); 671 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 672 CX_TEST_ASSERT(obj != NULL); 673 CX_TEST_ASSERT(cxJsonIsArray(obj)); 674 cxJsonValueFree(obj); 675 676 cxJsonFill(&json, nullstr); 677 result = cxJsonNext(&json, &obj); 678 CX_TEST_ASSERT(result == CX_JSON_NO_DATA); 679 CX_TEST_ASSERT(obj != NULL); 680 CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); 681 } 682 cxJsonDestroy(&json); 683 } 684 685 CX_TEST(test_json_object_error) { 686 cxstring text0 = cx_str( 687 "{\n" 688 "\t\"message\":\"success\",\n" 689 "\t\"data\":{\n" 690 "\t\t\"obj\":{\n" 691 "\t\t\t\"array\": [1, 2, 3, ?syntaxerror? ]\n" 692 "\t\t}\n" 693 "\t},\n" 694 "\t\"timestamp\":1729348561,\n" 695 "}" 696 ); 697 cxstring text1 = cx_str("{ \"string\" }"); 698 cxstring text2 = cx_str("{ \"a\" : }"); 699 cxstring text3 = cx_str("{ \"a\" : \"b\" ]"); 700 cxstring text4 = cx_str("{ \"name\": \"value\" ]"); 701 702 cxstring tests[] = { text0, text1, text2, text3, text4 }; 703 CxJsonStatus errors[] = { 704 CX_JSON_FORMAT_ERROR_NUMBER, 705 CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN, 706 CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN, 707 CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN, 708 CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN 709 }; 710 711 CX_TEST_DO { 712 CxJsonStatus result; 713 CxJson json; 714 CxJsonValue *obj = NULL; 715 716 for(int i=0;i<5;i++) { 717 cxJsonInit(&json, NULL); 718 cxJsonFill(&json, tests[i]); 719 result = cxJsonNext(&json, &obj); 720 721 CX_TEST_ASSERT(result == errors[i]); 722 CX_TEST_ASSERT(obj != NULL && obj->type == CX_JSON_NOTHING); 723 cxJsonDestroy(&json); 724 } 725 } 726 } 727 728 CX_TEST(test_json_object_remove_member) { 729 CxTestingAllocator talloc; 730 cx_testing_allocator_init(&talloc); 731 const CxAllocator *alloc = &talloc.base; 732 CX_TEST_DO { 733 CxJson json; 734 cxJsonInit(&json, alloc); 735 cxJsonFill(&json, 736 "{\n" 737 "\t\"message\":\"success\",\n" 738 "\t\"data\":{\n" 739 "\t\t\"obj\":{\n" 740 "\t\t\t\"array\": [1, 2, 3]\n" 741 "\t\t}\n" 742 "\t},\n" 743 "\t\"timestamp\":1729348561\n" 744 "}" 745 ); 746 CxJsonValue *obj; 747 CX_TEST_ASSERT(CX_JSON_NO_ERROR == cxJsonNext(&json, &obj)); 748 cxJsonDestroy(&json); 749 750 CX_TEST_ASSERT(cxJsonIsObject(cxJsonObjGet(obj, "data"))); 751 CxJsonValue *data = cxJsonObjRemove(obj, "data"); 752 CX_TEST_ASSERT(cxJsonIsObject(data)); 753 CX_TEST_ASSERT(!cxJsonIsObject(cxJsonObjGet(obj, "data"))); 754 CX_TEST_ASSERT(cxJsonIsObject(cxJsonObjGet(data, "obj"))); 755 756 CX_TEST_ASSERT(NULL == cxJsonObjRemove(obj, "data")); 757 758 // does not verify, yet, because we extracted an object 759 cxJsonValueFree(obj); 760 CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc)); 761 762 cxJsonValueFree(data); 763 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 764 } 765 cx_testing_allocator_destroy(&talloc); 766 } 767 768 CX_TEST(test_json_large_nesting_depth) { 769 CxJson json; 770 CxJsonValue *d1; 771 cxstring text = cx_str("{\"test\": [{},{\"foo\": [[{\"bar\":[4, 2, [null, {\"key\": 47}]]}]]}]}"); 772 CX_TEST_DO { 773 cxJsonInit(&json, NULL); 774 cxJsonFill(&json, text); 775 cxJsonNext(&json, &d1); 776 777 CX_TEST_ASSERT(d1 != NULL); 778 CX_TEST_ASSERT(cxJsonIsObject(d1)); 779 CxJsonValue *d2 = cxJsonObjGet(d1, "test"); 780 CX_TEST_ASSERT(cxJsonIsArray(d2)); 781 CX_TEST_ASSERT(cxJsonArrSize(d2) == 2); 782 CxJsonValue *d3 = cxJsonArrGet(d2, 1); 783 CX_TEST_ASSERT(cxJsonIsObject(d3)); 784 CxJsonValue *d4 = cxJsonObjGet(d3, "foo"); 785 CX_TEST_ASSERT(cxJsonIsArray(d4)); 786 CX_TEST_ASSERT(cxJsonArrSize(d4) == 1); 787 CxJsonValue *d5 = cxJsonArrGet(d4, 0); 788 CX_TEST_ASSERT(cxJsonIsArray(d5)); 789 CX_TEST_ASSERT(cxJsonArrSize(d5) == 1); 790 CxJsonValue *d6 = cxJsonArrGet(d5, 0); 791 CX_TEST_ASSERT(cxJsonIsObject(d6)); 792 CxJsonValue *d7 = cxJsonObjGet(d6, "bar"); 793 CX_TEST_ASSERT(cxJsonIsArray(d7)); 794 CX_TEST_ASSERT(cxJsonArrSize(d7) == 3); 795 CxJsonValue *d8 = cxJsonArrGet(d7, 2); 796 CX_TEST_ASSERT(cxJsonIsArray(d8)); 797 CX_TEST_ASSERT(cxJsonArrSize(d8) == 2); 798 CxJsonValue *d9a = cxJsonArrGet(d8, 0); 799 CX_TEST_ASSERT(cxJsonIsNull(d9a)); 800 CxJsonValue *d9b = cxJsonArrGet(d8, 1); 801 CX_TEST_ASSERT(cxJsonIsObject(d9b)); 802 CxJsonValue *d10 = cxJsonObjGet(d9b, "key"); 803 CX_TEST_ASSERT(cxJsonIsInteger(d10)); 804 CX_TEST_ASSERT(cxJsonAsInteger(d10) == 47); 805 806 CX_TEST_ASSERT(json.states != json.states_internal); 807 CX_TEST_ASSERT(json.states_capacity > cx_nmemb(json.states_internal)); 808 809 cxJsonValueFree(d1); 810 cxJsonDestroy(&json); 811 } 812 } 813 814 CX_TEST(test_json_number) { 815 CxJson json; 816 cxJsonInit(&json, NULL); 817 CX_TEST_DO { 818 CxJsonValue *v; 819 CxJsonStatus result; 820 821 cxJsonFill(&json, "3.1415 "); 822 result = cxJsonNext(&json, &v); 823 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 824 CX_TEST_ASSERT(cxJsonIsNumber(v)); 825 CX_TEST_ASSERT(0 == cx_vcmp_double(cxJsonAsDouble(v), 3.1415)); 826 cxJsonValueFree(v); 827 828 cxJsonFill(&json, "-47.11e2 "); 829 result = cxJsonNext(&json, &v); 830 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 831 CX_TEST_ASSERT(cxJsonIsNumber(v)); 832 CX_TEST_ASSERT(0 == cx_vcmp_double(cxJsonAsDouble(v), -4711.0)); 833 cxJsonValueFree(v); 834 835 cxJsonFill(&json, "0.815e-3 "); 836 result = cxJsonNext(&json, &v); 837 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 838 CX_TEST_ASSERT(cxJsonIsNumber(v)); 839 CX_TEST_ASSERT(0 == cx_vcmp_double(cxJsonAsDouble(v), 0.000815)); 840 cxJsonValueFree(v); 841 842 cxJsonFill(&json, "1.23E4 "); 843 result = cxJsonNext(&json, &v); 844 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 845 CX_TEST_ASSERT(cxJsonIsNumber(v)); 846 CX_TEST_ASSERT(cxJsonAsInteger(v) == 12300); 847 CX_TEST_ASSERT(cxJsonAsDouble(v) == 12300.0); 848 cxJsonValueFree(v); 849 850 cxJsonFill(&json, "18446744073709551615.0123456789 "); 851 result = cxJsonNext(&json, &v); 852 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 853 CX_TEST_ASSERT(cxJsonIsNumber(v)); 854 // be as precise as possible 855 // TODO: this might produce format error / out of range in future implementations 856 CX_TEST_ASSERT(0 == cx_vcmp_double(cxJsonAsDouble(v), 1.8446744073709552e+19)); 857 cxJsonValueFree(v); 858 } 859 cxJsonDestroy(&json); 860 } 861 862 CX_TEST(test_json_number_format_errors) { 863 CxJson json; 864 cxJsonInit(&json, NULL); 865 CX_TEST_DO { 866 CxJsonValue *v; 867 CxJsonStatus result; 868 869 cxJsonFill(&json, "+3.1415 "); 870 result = cxJsonNext(&json, &v); 871 CX_TEST_ASSERTM(result == CX_JSON_FORMAT_ERROR_NUMBER, 872 "leading plus is not RFC-8259 compliant"); 873 CX_TEST_ASSERT(v->type == CX_JSON_NOTHING); 874 cxJsonReset(&json); 875 876 cxJsonFill(&json, "0.815e-3.0 "); 877 result = cxJsonNext(&json, &v); 878 CX_TEST_ASSERTM(result == CX_JSON_FORMAT_ERROR_NUMBER, 879 "exponent must be an integer"); 880 CX_TEST_ASSERT(v->type == CX_JSON_NOTHING); 881 cxJsonReset(&json); 882 883 cxJsonFill(&json, "3.14e "); 884 result = cxJsonNext(&json, &v); 885 CX_TEST_ASSERTM(result == CX_JSON_FORMAT_ERROR_NUMBER, 886 "exponent cannot be empty"); 887 CX_TEST_ASSERT(v->type == CX_JSON_NOTHING); 888 cxJsonReset(&json); 889 890 cxJsonFill(&json, "3.14e~7 "); 891 result = cxJsonNext(&json, &v); 892 CX_TEST_ASSERTM(result == CX_JSON_FORMAT_ERROR_NUMBER, 893 "exponent cannot start with bullshit"); 894 CX_TEST_ASSERT(v->type == CX_JSON_NOTHING); 895 cxJsonReset(&json); 896 897 cxJsonFill(&json, "1.23e4f "); 898 result = cxJsonNext(&json, &v); 899 CX_TEST_ASSERTM(result == CX_JSON_FORMAT_ERROR_NUMBER, 900 "non-digits in exponent"); 901 CX_TEST_ASSERT(v->type == CX_JSON_NOTHING); 902 cxJsonReset(&json); 903 904 cxJsonFill(&json, "1.23f "); 905 result = cxJsonNext(&json, &v); 906 CX_TEST_ASSERTM(result == CX_JSON_FORMAT_ERROR_NUMBER, 907 "non-digits in value"); 908 CX_TEST_ASSERT(v->type == CX_JSON_NOTHING); 909 cxJsonReset(&json); 910 911 cxJsonFill(&json, "1.23.45 "); 912 result = cxJsonNext(&json, &v); 913 CX_TEST_ASSERTM(result == CX_JSON_FORMAT_ERROR_NUMBER, 914 "multiple decimal separators"); 915 CX_TEST_ASSERT(v->type == CX_JSON_NOTHING); 916 cxJsonReset(&json); 917 918 cxJsonFill(&json, "184467440737095516150123456789 "); 919 result = cxJsonNext(&json, &v); 920 CX_TEST_ASSERTM(result == CX_JSON_FORMAT_ERROR_NUMBER, 921 "30 digit int does not fit into 64-bit int"); 922 CX_TEST_ASSERT(v->type == CX_JSON_NOTHING); 923 cxJsonReset(&json); 924 } 925 cxJsonDestroy(&json); 926 } 927 928 CX_TEST(test_json_multiple_values) { 929 CxJson json; 930 cxJsonInit(&json, NULL); 931 CX_TEST_DO { 932 CxJsonValue *v; 933 CxJsonStatus result; 934 935 // read number 936 cxJsonFill(&json, "10\n"); 937 result = cxJsonNext(&json, &v); 938 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 939 CX_TEST_ASSERT(cxJsonIsNumber(v)); 940 CX_TEST_ASSERT(cxJsonAsInteger(v) == 10); 941 cxJsonValueFree(v); 942 // read remaining '\n' 943 result = cxJsonNext(&json, &v); 944 CX_TEST_ASSERT(result == CX_JSON_NO_DATA); 945 // read string 946 cxJsonFill(&json, "\"hello world\"\n"); 947 result = cxJsonNext(&json, &v); 948 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 949 CX_TEST_ASSERT(cxJsonIsString(v)); 950 CX_TEST_ASSERT(!cx_strcmp(cxJsonAsCxString(v), "hello world")); 951 cxJsonValueFree(v); 952 // don't process the remaining newline this time 953 // read obj 954 cxJsonFill(&json, "{ \"value\": \"test\" }\n"); 955 result = cxJsonNext(&json, &v); 956 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 957 CX_TEST_ASSERT(cxJsonIsObject(v)); 958 CxJsonValue *value = cxJsonObjGet(v, "value"); 959 CX_TEST_ASSERT(cxJsonAsString(value)); 960 cxJsonValueFree(v); 961 // read array 962 cxJsonFill(&json, "[ 0, 1, 2, 3, 4, 5 ]\n"); 963 result = cxJsonNext(&json, &v); 964 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 965 CX_TEST_ASSERT(cxJsonIsArray(v)); 966 CxJsonValue *a0 = cxJsonArrGet(v, 0); 967 CxJsonValue *a3 = cxJsonArrGet(v, 3); 968 CX_TEST_ASSERT(cxJsonIsNumber(a0)); 969 CX_TEST_ASSERT(cxJsonAsInteger(a0) == 0); 970 CX_TEST_ASSERT(cxJsonIsNumber(a3)); 971 CX_TEST_ASSERT(cxJsonAsInteger(a3) == 3); 972 cxJsonValueFree(v); 973 // read literal 974 cxJsonFill(&json, "true\n"); 975 result = cxJsonNext(&json, &v); 976 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 977 CX_TEST_ASSERT(cxJsonIsLiteral(v)); 978 CX_TEST_ASSERT(cxJsonIsBool(v)); 979 CX_TEST_ASSERT(cxJsonIsTrue(v)); 980 CX_TEST_ASSERT(cxJsonAsBool(v)); 981 cxJsonValueFree(v); 982 cxJsonFill(&json, "false\n"); 983 result = cxJsonNext(&json, &v); 984 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 985 CX_TEST_ASSERT(cxJsonIsLiteral(v)); 986 CX_TEST_ASSERT(cxJsonIsBool(v)); 987 CX_TEST_ASSERT(cxJsonIsFalse(v)); 988 CX_TEST_ASSERT(!cxJsonAsBool(v)); 989 cxJsonValueFree(v); 990 cxJsonFill(&json, "null\n"); 991 result = cxJsonNext(&json, &v); 992 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 993 CX_TEST_ASSERT(cxJsonIsLiteral(v)); 994 CX_TEST_ASSERT(!cxJsonIsBool(v)); 995 CX_TEST_ASSERT(cxJsonIsNull(v)); 996 cxJsonValueFree(v); 997 } 998 cxJsonDestroy(&json); 999 } 1000 1001 CX_TEST(test_json_array) { 1002 CxTestingAllocator talloc; 1003 cx_testing_allocator_init(&talloc); 1004 const CxAllocator *allocator = &talloc.base; 1005 CX_TEST_DO { 1006 CxJsonValue *arr = cxJsonCreateArr(allocator); 1007 cxJsonArrAddIntegers(arr, (int64_t[]){ 0, 3, 6, 9, 12, 15}, 6); 1008 CX_TEST_ASSERT(cxJsonArrSize(arr) == 6); 1009 CxJsonValue *e = cxJsonArrGet(arr, 3); 1010 CX_TEST_ASSERT(cxJsonIsNumber(e)); 1011 CX_TEST_ASSERT(cxJsonAsInteger(e) == 9); 1012 CX_TEST_ASSERT(cxJsonAsDouble(e) == 9.0); 1013 CX_TEST_ASSERT(cxJsonArrSize(arr) == 6); 1014 e = cxJsonArrGet(arr, 6); 1015 CX_TEST_ASSERT(!cxJsonIsNumber(e)); 1016 // also not null, because "nothing" is not null 1017 CX_TEST_ASSERT(!cxJsonIsNull(e)); 1018 CX_TEST_ASSERT(e->type == CX_JSON_NOTHING); 1019 CxJsonValue *removed = cxJsonArrRemove(arr, 2); 1020 CX_TEST_ASSERT(cxJsonIsNumber(removed)); 1021 CX_TEST_ASSERT(cxJsonAsInteger(removed) == 6); 1022 CX_TEST_ASSERT(cxJsonArrSize(arr) == 5); 1023 e = cxJsonArrGet(arr, 3); 1024 CX_TEST_ASSERT(cxJsonIsNumber(e)); 1025 CX_TEST_ASSERT(cxJsonAsInteger(e) == 12); 1026 e = cxJsonArrRemove(arr, 5); 1027 CX_TEST_ASSERT(e == NULL); 1028 cxJsonValueFree(arr); 1029 // the removed element still needs to be freed 1030 CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc)); 1031 cxJsonValueFree(removed); 1032 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 1033 } 1034 cx_testing_allocator_destroy(&talloc); 1035 } 1036 1037 CX_TEST(test_json_array_iterator) { 1038 CxJson json; 1039 cxJsonInit(&json, NULL); 1040 CX_TEST_DO { 1041 CxJsonValue *v; 1042 CxJsonStatus result; 1043 cxJsonFill(&json, "[ 0, 3, 6, 9, 12, 15 ]\n"); 1044 result = cxJsonNext(&json, &v); 1045 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 1046 CX_TEST_ASSERT(cxJsonIsArray(v)); 1047 CxIterator iter = cxJsonArrIter(v); 1048 unsigned i = 0; 1049 cx_foreach(CxJsonValue*, elem, iter) { 1050 CX_TEST_ASSERT(cxJsonIsNumber(elem)); 1051 CX_TEST_ASSERT(i == cxJsonAsInteger(elem)); 1052 i += 3; 1053 } 1054 cxJsonValueFree(v); 1055 } 1056 cxJsonDestroy(&json); 1057 } 1058 1059 CX_TEST(test_json_allocator) { 1060 CxTestingAllocator talloc; 1061 cx_testing_allocator_init(&talloc); 1062 CxAllocator *allocator = &talloc.base; 1063 1064 cxstring text = cx_str( 1065 "{\n" 1066 "\t\"message\":\"success\",\n" 1067 "\t\"data\":[\"value1\",{\"x\":123, \"y\":523 }]\n" 1068 "}" 1069 ); 1070 1071 CX_TEST_DO { 1072 CxJson json; 1073 cxJsonInit(&json, allocator); 1074 cxJsonFill(&json, text); 1075 1076 CxJsonValue *obj; 1077 CxJsonStatus result = cxJsonNext(&json, &obj); 1078 CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); 1079 CX_TEST_ASSERT(obj->allocator == allocator); 1080 1081 // this recursively frees everything 1082 cxJsonValueFree(obj); 1083 cxJsonDestroy(&json); 1084 1085 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 1086 } 1087 cx_testing_allocator_destroy(&talloc); 1088 } 1089 1090 CX_TEST(test_json_allocator_parse_error) { 1091 CxTestingAllocator talloc; 1092 cx_testing_allocator_init(&talloc); 1093 CxAllocator *allocator = &talloc.base; 1094 1095 cxstring text = cx_str( 1096 "{\n" 1097 "\t\"message\":\"success\"\n" // <-- missing comma 1098 "\t\"data\":[\"value1\",{\"x\":123, \"y\":523 }]\n" 1099 "}" 1100 ); 1101 1102 CX_TEST_DO { 1103 CxJson json; 1104 cxJsonInit(&json, allocator); 1105 cxJsonFill(&json, text); 1106 1107 CxJsonValue *obj = NULL; 1108 CxJsonStatus result = cxJsonNext(&json, &obj); 1109 CX_TEST_ASSERT(result == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); 1110 CX_TEST_ASSERT(obj != NULL && obj->type == CX_JSON_NOTHING); 1111 1112 // clean-up any left-over memory 1113 cxJsonDestroy(&json); 1114 1115 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 1116 } 1117 cx_testing_allocator_destroy(&talloc); 1118 } 1119 1120 CX_TEST(test_json_create_value) { 1121 CxTestingAllocator talloc; 1122 cx_testing_allocator_init(&talloc); 1123 CxAllocator *allocator = &talloc.base; 1124 CX_TEST_DO { 1125 /* 1126 * This is the value we want to create in this test: 1127 * 1128 * { 1129 * "bool": false, 1130 * "int": 47, 1131 * "strings": [ "hello", "world" ], 1132 * "nested": { 1133 * "string": "test", 1134 * "floats": [ 3.1415, 47.11, 8.15 ], 1135 * "ints": [ 4, 8, 15, 16, 23, 42 ], 1136 * "literals": [ true, null, false ] 1137 * } 1138 * } 1139 */ 1140 1141 1142 // create the object 1143 CxJsonValue *obj = cxJsonCreateObj(allocator); 1144 CX_TEST_ASSERT(obj != NULL); 1145 CX_TEST_ASSERT(cxJsonIsObject(obj)); 1146 CX_TEST_ASSERT(obj->allocator == allocator); 1147 1148 // add the members 1149 { 1150 cxJsonObjPutLiteral(obj, "bool", CX_JSON_FALSE); 1151 cxJsonObjPutInteger(obj, "int", 47); 1152 CxJsonValue *strings = cxJsonObjPutArr(obj, "strings"); 1153 CX_TEST_ASSERT(strings != NULL); 1154 CX_TEST_ASSERT(cxJsonIsArray(strings)); 1155 const char* str[] = {"hello", "world"}; 1156 CX_TEST_ASSERT(0 == cxJsonArrAddStrings(strings, str, 2)); 1157 1158 CxJsonValue *nested = cxJsonObjPutObj(obj, "nested"); 1159 CX_TEST_ASSERT(nested != NULL); 1160 CX_TEST_ASSERT(cxJsonIsObject(nested)); 1161 cxJsonObjPutString(nested, "string", "test"); 1162 1163 cxJsonArrAddNumbers(cxJsonObjPutArr(nested, "floats"), 1164 (double[]){3.1415, 47.11, 8.15}, 3); 1165 cxJsonArrAddIntegers(cxJsonObjPutArr(nested, "ints"), 1166 (int64_t[]){4, 8, 15, 16, 23, 42}, 6); 1167 cxJsonArrAddLiterals(cxJsonObjPutArr(nested, "literals"), 1168 (CxJsonLiteral[]){CX_JSON_TRUE, CX_JSON_NULL, CX_JSON_FALSE}, 3); 1169 } 1170 1171 // verify the contents 1172 { 1173 CX_TEST_ASSERT(cxJsonIsFalse(cxJsonObjGet(obj, "bool"))); 1174 CX_TEST_ASSERT(47 == cxJsonAsInteger(cxJsonObjGet(obj, "int"))); 1175 CxJsonValue *strings = cxJsonObjGet(obj, "strings"); 1176 CX_TEST_ASSERT(cxJsonIsArray(strings)); 1177 CX_TEST_ASSERT(2 == cxJsonArrSize(strings)); 1178 CX_TEST_ASSERT(0 == cx_strcmp("hello", cxJsonAsString(cxJsonArrGet(strings, 0)))); 1179 CX_TEST_ASSERT(0 == cx_strcmp("world", cxJsonAsString(cxJsonArrGet(strings, 1)))); 1180 1181 CxJsonValue *nested = cxJsonObjGet(obj, "nested"); 1182 CX_TEST_ASSERT(cxJsonIsObject(nested)); 1183 CX_TEST_ASSERT(0 == strcmp("test", cxJsonAsString(cxJsonObjGet(nested, "string")))); 1184 CxJsonValue *floats = cxJsonObjGet(nested, "floats"); 1185 CX_TEST_ASSERT(cxJsonIsArray(floats)); 1186 CX_TEST_ASSERT(3 == cxJsonArrSize(floats)); 1187 CX_TEST_ASSERT(3.1415 == cxJsonAsDouble(cxJsonArrGet(floats, 0))); 1188 CX_TEST_ASSERT(47.11 == cxJsonAsDouble(cxJsonArrGet(floats, 1))); 1189 CX_TEST_ASSERT(8.15 == cxJsonAsDouble(cxJsonArrGet(floats, 2))); 1190 CxJsonValue *ints = cxJsonObjGet(nested, "ints"); 1191 CX_TEST_ASSERT(cxJsonIsArray(ints)); 1192 CX_TEST_ASSERT(6 == cxJsonArrSize(ints)); 1193 CX_TEST_ASSERT(4 == cxJsonAsInteger(cxJsonArrGet(ints, 0))); 1194 CX_TEST_ASSERT(8 == cxJsonAsInteger(cxJsonArrGet(ints, 1))); 1195 CX_TEST_ASSERT(15 == cxJsonAsInteger(cxJsonArrGet(ints, 2))); 1196 CX_TEST_ASSERT(16 == cxJsonAsInteger(cxJsonArrGet(ints, 3))); 1197 CX_TEST_ASSERT(23 == cxJsonAsInteger(cxJsonArrGet(ints, 4))); 1198 CX_TEST_ASSERT(42 == cxJsonAsInteger(cxJsonArrGet(ints, 5))); 1199 CxJsonValue *literals = cxJsonObjGet(nested, "literals"); 1200 CX_TEST_ASSERT(cxJsonIsArray(literals)); 1201 CX_TEST_ASSERT(3 == cxJsonArrSize(literals)); 1202 CX_TEST_ASSERT(cxJsonIsTrue(cxJsonArrGet(literals, 0))); 1203 CX_TEST_ASSERT(cxJsonIsNull(cxJsonArrGet(literals, 1))); 1204 CX_TEST_ASSERT(cxJsonIsFalse(cxJsonArrGet(literals, 2))); 1205 } 1206 1207 // destroy the value and verify the allocations 1208 cxJsonValueFree(obj); 1209 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 1210 } 1211 cx_testing_allocator_destroy(&talloc); 1212 } 1213 1214 CX_TEST(test_json_overwrite_value) { 1215 CxTestingAllocator talloc; 1216 cx_testing_allocator_init(&talloc); 1217 CxAllocator *allocator = &talloc.base; 1218 CX_TEST_DO { 1219 CxJsonValue *obj = cxJsonCreateObj(allocator); 1220 1221 // put some values 1222 cxJsonObjPutInteger(obj, "test1", 1); 1223 cxJsonObjPutInteger(obj, "test2", 2); 1224 cxJsonObjPutInteger(obj, "test3", 3); 1225 1226 // overwrite test2 1227 cxJsonObjPutInteger(obj, "test2", 0); 1228 1229 // verify the values 1230 CxMapIterator iter = cxJsonObjIter(obj); 1231 bool found[5] = {0}; 1232 cx_foreach(CxMapEntry *, ov, iter) { 1233 CxJsonValue *v = ov->value; 1234 CX_TEST_ASSERT(cxJsonIsInteger(v)); 1235 int64_t i = cxJsonAsInteger(v); 1236 CX_TEST_ASSERT(i >= 0 && i <= 4); 1237 found[i] = true; 1238 } 1239 CX_TEST_ASSERT(found[0]); 1240 CX_TEST_ASSERT(found[1]); 1241 CX_TEST_ASSERT(!found[2]); 1242 CX_TEST_ASSERT(found[3]); 1243 CX_TEST_ASSERT(!found[4]); 1244 1245 // destroy the value and verify the allocations 1246 cxJsonValueFree(obj); 1247 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 1248 } 1249 cx_testing_allocator_destroy(&talloc); 1250 } 1251 1252 CX_TEST_SUBROUTINE(test_json_write_sub, 1253 const CxAllocator *allocator, 1254 cxstring expected, 1255 const CxJsonWriter *writer 1256 ) { 1257 // create the value 1258 CxJsonValue *obj = cxJsonCreateObj(allocator); 1259 cxJsonObjPutLiteral(obj, "bool", CX_JSON_FALSE); 1260 cxJsonObjPutNumber(obj, "int", 47); // purposely use PutNumber to put an int 1261 CxJsonValue *strings = cxJsonObjPutArr(obj, "strings"); 1262 cxJsonArrAddCxStrings(strings, (cxstring[]) {CX_STR("hello"), CX_STR("world")}, 2); 1263 CxJsonValue *nested = cxJsonObjPutObj(obj, "nested"); 1264 CxJsonValue *objects = cxJsonObjPutArr(nested, "objects"); 1265 CxJsonValue *obj_in_arr[2] = {cxJsonCreateObj(allocator), cxJsonCreateObj(allocator)}; 1266 cxJsonObjPutInteger(obj_in_arr[0], "name1", 1); 1267 cxJsonObjPutInteger(obj_in_arr[0], "name2", 3); 1268 cxJsonObjPutInteger(obj_in_arr[1], "name2", 7); 1269 cxJsonObjPutInteger(obj_in_arr[1], "name1", 3); 1270 cxJsonArrAddValues(objects, obj_in_arr, 2); 1271 cxJsonArrAddNumbers(cxJsonObjPutArr(nested, "floats"), 1272 (double[]){3.1415, 47.11, 8.15}, 3); 1273 cxJsonArrAddLiterals(cxJsonObjPutArr(nested, "literals"), 1274 (CxJsonLiteral[]){CX_JSON_TRUE, CX_JSON_NULL, CX_JSON_FALSE}, 3); 1275 CxJsonValue *ints = cxJsonObjPutArr(nested, "ints"); 1276 cxJsonArrAddIntegers(ints, (int64_t[]){4, 8, 15}, 3); 1277 CxJsonValue *nested_array = cxJsonCreateArr(allocator); 1278 cxJsonArrAddValues(ints, &nested_array, 1); 1279 cxJsonArrAddIntegers(nested_array, (int64_t[]){16, 23}, 2); 1280 cxJsonArrAddIntegers(ints, (int64_t[]){42}, 1); 1281 1282 // write it to a buffer 1283 CxBuffer buf; 1284 cxBufferInit(&buf, NULL, 512, NULL, CX_BUFFER_DEFAULT); 1285 int result = cxJsonWrite(&buf, obj, cxBufferWriteFunc, writer); 1286 cxBufferTerminate(&buf); // makes debugging easier 1287 CX_TEST_ASSERT(result == 0); 1288 1289 // compare the string 1290 CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), expected)); 1291 1292 // destroy everything 1293 cxBufferDestroy(&buf); 1294 cxJsonValueFree(obj); 1295 } 1296 1297 CX_TEST(test_json_write_default_format) { 1298 CxTestingAllocator talloc; 1299 cx_testing_allocator_init(&talloc); 1300 CxAllocator *allocator = &talloc.base; 1301 CX_TEST_DO { 1302 // expected value 1303 cxstring expected = cx_str( 1304 "{\"bool\":false," 1305 "\"int\":47," 1306 "\"strings\":[\"hello\",\"world\"]," 1307 "\"nested\":{" 1308 "\"objects\":[{" 1309 "\"name1\":1," 1310 "\"name2\":3" 1311 "},{" 1312 "\"name2\":7," 1313 "\"name1\":3" 1314 "}]," 1315 "\"floats\":[3.1415,47.11,8.15]," 1316 "\"literals\":[true,null,false]," 1317 "\"ints\":[4,8,15,[16,23],42]" 1318 "}" 1319 "}" 1320 ); 1321 1322 CxJsonWriter writer = cxJsonWriterCompact(); 1323 CX_TEST_CALL_SUBROUTINE(test_json_write_sub, allocator, expected, &writer); 1324 // try again, but this time with implicitly defaulted writer 1325 CX_TEST_CALL_SUBROUTINE(test_json_write_sub, allocator, expected, NULL); 1326 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 1327 } 1328 cx_testing_allocator_destroy(&talloc); 1329 } 1330 1331 CX_TEST(test_json_write_pretty_default_spaces) { 1332 CxTestingAllocator talloc; 1333 cx_testing_allocator_init(&talloc); 1334 CxAllocator *allocator = &talloc.base; 1335 CX_TEST_DO { 1336 cxstring expected = cx_str( 1337 "{\n" 1338 " \"bool\": false,\n" 1339 " \"int\": 47,\n" 1340 " \"strings\": [\"hello\", \"world\"],\n" 1341 " \"nested\": {\n" 1342 " \"objects\": [{\n" 1343 " \"name1\": 1,\n" 1344 " \"name2\": 3\n" 1345 " }, {\n" 1346 " \"name2\": 7,\n" 1347 " \"name1\": 3\n" 1348 " }],\n" 1349 " \"floats\": [3.1415, 47.11, 8.15],\n" 1350 " \"literals\": [true, null, false],\n" 1351 " \"ints\": [4, 8, 15, [16, 23], 42]\n" 1352 " }\n" 1353 "}" 1354 ); 1355 1356 CxJsonWriter writer = cxJsonWriterPretty(true); 1357 CX_TEST_CALL_SUBROUTINE(test_json_write_sub, allocator, expected, &writer); 1358 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 1359 } 1360 cx_testing_allocator_destroy(&talloc); 1361 } 1362 1363 CX_TEST(test_json_write_pretty_default_tabs) { 1364 CxTestingAllocator talloc; 1365 cx_testing_allocator_init(&talloc); 1366 CxAllocator *allocator = &talloc.base; 1367 CX_TEST_DO { 1368 cxstring expected = cx_str( 1369 "{\n" 1370 "\t\"bool\": false,\n" 1371 "\t\"int\": 47,\n" 1372 "\t\"strings\": [\"hello\", \"world\"],\n" 1373 "\t\"nested\": {\n" 1374 "\t\t\"objects\": [{\n" 1375 "\t\t\t\"name1\": 1,\n" 1376 "\t\t\t\"name2\": 3\n" 1377 "\t\t}, {\n" 1378 "\t\t\t\"name2\": 7,\n" 1379 "\t\t\t\"name1\": 3\n" 1380 "\t\t}],\n" 1381 "\t\t\"floats\": [3.1415, 47.11, 8.15],\n" 1382 "\t\t\"literals\": [true, null, false],\n" 1383 "\t\t\"ints\": [4, 8, 15, [16, 23], 42]\n" 1384 "\t}\n" 1385 "}" 1386 ); 1387 CxJsonWriter writer = cxJsonWriterPretty(false); 1388 CX_TEST_CALL_SUBROUTINE(test_json_write_sub, allocator, expected, &writer); 1389 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 1390 } 1391 cx_testing_allocator_destroy(&talloc); 1392 } 1393 1394 CX_TEST(test_json_write_pretty_deep_nesting) { 1395 CxTestingAllocator talloc; 1396 cx_testing_allocator_init(&talloc); 1397 CxAllocator *allocator = &talloc.base; 1398 CX_TEST_DO { 1399 cxstring expected = cx_str( 1400 "{\n" 1401 " \"test\": {\n" 1402 " \"test\": {\n" 1403 " \"test\": {\n" 1404 " \"test\": {\n" 1405 " \"test\": {\n" 1406 " \"test\": 47\n" 1407 " }\n" 1408 " }\n" 1409 " }\n" 1410 " }\n" 1411 " }\n" 1412 "}" 1413 ); 1414 1415 CxJsonValue *obj = cxJsonCreateObj(allocator); 1416 CxJsonValue *test = obj; 1417 for (unsigned i = 0 ; i < 5 ; i++) { 1418 test = cxJsonObjPutObj(test, "test"); 1419 } 1420 cxJsonObjPutInteger(test, "test", 47); 1421 1422 CxJsonWriter writer = cxJsonWriterPretty(true); 1423 writer.indent = 8; 1424 CxBuffer buf; 1425 cxBufferInit(&buf, NULL, 512, NULL, CX_BUFFER_DEFAULT); 1426 int result = cxJsonWrite(&buf, obj, cxBufferWriteFunc, &writer); 1427 cxBufferTerminate(&buf); // makes debugging easier 1428 CX_TEST_ASSERT(result == 0); 1429 1430 // compare the string 1431 CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), expected)); 1432 1433 // destroy everything 1434 cxBufferDestroy(&buf); 1435 cxJsonValueFree(obj); 1436 1437 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 1438 } 1439 cx_testing_allocator_destroy(&talloc); 1440 } 1441 1442 CX_TEST(test_json_write_frac_max_digits) { 1443 CxJsonValue* num = cxJsonCreateNumber(NULL, 3.141592653589793); 1444 CxJsonWriter writer = cxJsonWriterCompact(); 1445 CxBuffer buf; 1446 cxBufferInit(&buf, NULL, 32, NULL, 0); 1447 CX_TEST_DO { 1448 // test default settings (6 digits) 1449 CX_TEST_ASSERT(0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer)); 1450 CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "3.141592")); 1451 1452 // test too many digits 1453 cxBufferReset(&buf); 1454 writer.frac_max_digits = 200; 1455 CX_TEST_ASSERT(0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer)); 1456 CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "3.141592653589793")); 1457 1458 // test 0 digits 1459 cxBufferReset(&buf); 1460 writer.frac_max_digits = 0; 1461 CX_TEST_ASSERT(0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer)); 1462 CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "3")); 1463 1464 // test 2 digits 1465 cxBufferReset(&buf); 1466 writer.frac_max_digits = 2; 1467 CX_TEST_ASSERT(0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer)); 1468 CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "3.14")); 1469 1470 // test 3 digits 1471 cxBufferReset(&buf); 1472 writer.frac_max_digits = 3; 1473 CX_TEST_ASSERT(0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer)); 1474 CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "3.141")); 1475 1476 // test 6 digits, but two are left of the decimal point 1477 num->number = 47.110815; 1478 cxBufferReset(&buf); 1479 writer.frac_max_digits = 6; 1480 CX_TEST_ASSERT(0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer)); 1481 CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "47.110815")); 1482 1483 // test 4 digits with exponent 1484 num->number = 5.11223344e23; 1485 cxBufferReset(&buf); 1486 writer.frac_max_digits = 4; 1487 CX_TEST_ASSERT(0 == cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer)); 1488 CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "5.1122e+23")); 1489 } 1490 cxBufferDestroy(&buf); 1491 cxJsonValueFree(num); 1492 } 1493 1494 CX_TEST(test_json_write_string_escape) { 1495 /** 1496 * According to RFC-8259 we have to test the following characters: 1497 * " quotation mark 1498 * \ reverse solidus 1499 * / solidus ---> we make this optional, see test_json_write_solidus 1500 * b backspace 1501 * f form feed 1502 * n line feed 1503 * r carriage return 1504 * t tab 1505 * And all other control characters must be encoded uXXXX - in our example the bell character. 1506 * Also, all unicode characters are encoded that way - in our example the 'ö'. 1507 */ 1508 CxJsonValue* str = cxJsonCreateString(NULL, 1509 "hello\twörld\r\nthis is\\a \"string\"\b in \a string\f"); 1510 CxJsonWriter writer = cxJsonWriterCompact(); 1511 CxBuffer buf; 1512 cxBufferInit(&buf, NULL, 128, NULL, 0); 1513 CX_TEST_DO { 1514 CX_TEST_ASSERT(0 == cxJsonWrite(&buf, str, cxBufferWriteFunc, &writer)); 1515 CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), 1516 "\"hello\\twörld\\r\\nthis is\\\\a \\\"string\\\"\\b in \\u0007 string\\f\"")); 1517 } 1518 cxBufferDestroy(&buf); 1519 cxJsonValueFree(str); 1520 } 1521 1522 CX_TEST(test_json_write_name_escape) { 1523 CxJsonValue* obj = cxJsonCreateObj(NULL); 1524 cxJsonObjPutLiteral(obj, 1525 "hello\twörld\r\nthis is\\a \"string\"\b in \a string\f", CX_JSON_TRUE); 1526 CxJsonWriter writer = cxJsonWriterCompact(); 1527 CxBuffer buf; 1528 cxBufferInit(&buf, NULL, 128, NULL, 0); 1529 CX_TEST_DO { 1530 CX_TEST_ASSERT(0 == cxJsonWrite(&buf, obj, cxBufferWriteFunc, &writer)); 1531 CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), 1532 "{\"hello\\twörld\\r\\nthis is\\\\a \\\"string\\\"\\b in \\u0007 string\\f\":true}")); 1533 } 1534 cxBufferDestroy(&buf); 1535 cxJsonValueFree(obj); 1536 } 1537 1538 CX_TEST(test_json_write_solidus) { 1539 CxJsonValue* str = cxJsonCreateString(NULL,"test/solidus"); 1540 CxJsonWriter writer = cxJsonWriterCompact(); 1541 CxBuffer buf; 1542 cxBufferInit(&buf, NULL, 16, NULL, 0); 1543 CX_TEST_DO { 1544 // default: do not escape 1545 CX_TEST_ASSERT(0 == cxJsonWrite(&buf, str, cxBufferWriteFunc, &writer)); 1546 CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "\"test/solidus\"")); 1547 1548 // enable escaping 1549 writer.escape_slash = true; 1550 cxBufferReset(&buf); 1551 CX_TEST_ASSERT(0 == cxJsonWrite(&buf, str, cxBufferWriteFunc, &writer)); 1552 CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "\"test\\/solidus\"")); 1553 } 1554 cxBufferDestroy(&buf); 1555 cxJsonValueFree(str); 1556 } 1557 1558 CX_TEST(test_json_write_nothing) { 1559 CxBuffer buf; 1560 cxBufferInit(&buf, NULL, 16, NULL, 0); 1561 CX_TEST_DO { 1562 CxJsonValue nothing; 1563 nothing.type = CX_JSON_NOTHING; 1564 CX_TEST_ASSERT(0 == cxJsonWrite(&buf, &nothing, cxBufferWriteFunc, NULL)); 1565 CX_TEST_ASSERT(buf.size == 0); 1566 } 1567 cxBufferDestroy(&buf); 1568 } 1569 1570 CxTestSuite *cx_test_suite_json(void) { 1571 CxTestSuite *suite = cx_test_suite_new("json"); 1572 1573 cx_test_register(suite, test_json_init_default); 1574 cx_test_register(suite, test_json_simple_object); 1575 cx_test_register(suite, test_json_large_object); 1576 cx_test_register(suite, test_json_from_string); 1577 cx_test_register(suite, test_json_from_string_errors); 1578 cx_test_register(suite, test_json_from_string_multiple_values); 1579 cx_test_register(suite, test_json_from_string_untrimmed); 1580 cx_test_register(suite, test_json_escaped_strings); 1581 cx_test_register(suite, test_json_escaped_unicode_strings); 1582 cx_test_register(suite, test_json_escaped_unicode_malformed); 1583 cx_test_register(suite, test_json_escaped_end_of_string); 1584 cx_test_register(suite, test_json_object_incomplete_token); 1585 cx_test_register(suite, test_json_parenthesis_mismatch); 1586 cx_test_register(suite, test_json_object_name_is_no_string); 1587 cx_test_register(suite, test_json_token_wrongly_completed); 1588 cx_test_register(suite, test_json_object_error); 1589 cx_test_register(suite, test_json_object_remove_member); 1590 cx_test_register(suite, test_json_subsequent_fill); 1591 cx_test_register(suite, test_json_no_fill); 1592 cx_test_register(suite, test_json_null_fill); 1593 cx_test_register(suite, test_json_large_nesting_depth); 1594 cx_test_register(suite, test_json_number); 1595 cx_test_register(suite, test_json_number_format_errors); 1596 cx_test_register(suite, test_json_multiple_values); 1597 cx_test_register(suite, test_json_array); 1598 cx_test_register(suite, test_json_array_iterator); 1599 cx_test_register(suite, test_json_allocator); 1600 cx_test_register(suite, test_json_allocator_parse_error); 1601 cx_test_register(suite, test_json_create_value); 1602 cx_test_register(suite, test_json_overwrite_value); 1603 cx_test_register(suite, test_json_write_default_format); 1604 cx_test_register(suite, test_json_write_pretty_default_spaces); 1605 cx_test_register(suite, test_json_write_pretty_default_tabs); 1606 cx_test_register(suite, test_json_write_pretty_deep_nesting); 1607 cx_test_register(suite, test_json_write_frac_max_digits); 1608 cx_test_register(suite, test_json_write_string_escape); 1609 cx_test_register(suite, test_json_write_name_escape); 1610 cx_test_register(suite, test_json_write_solidus); 1611 cx_test_register(suite, test_json_write_nothing); 1612 1613 return suite; 1614 } 1615 1616