# HG changeset patch # User Mike Becker # Date 1764099327 -3600 # Node ID 4d641c6a2f820922eb5149afac2226164d0cd9ab # Parent 0dc866c7863b47e1685fc0a73b97c2e8d09ee488 implement better strings for json.c + complete test coverage diff -r 0dc866c7863b -r 4d641c6a2f82 docs/Writerside/topics/json.h.md --- a/docs/Writerside/topics/json.h.md Mon Nov 24 22:39:18 2025 +0100 +++ b/docs/Writerside/topics/json.h.md Tue Nov 25 20:35:27 2025 +0100 @@ -106,6 +106,8 @@ CxJsonValue *cxJsonArrRemove(const CxJsonValue *value, size_t index); +size_t cxJsonObjSize(const CxJsonValue *value); + CxJsonValue *cxJsonObjGet(const CxJsonValue *value, AnyStr name); CxJsonValue *cxJsonObjRemove(const CxJsonValue *value, AnyStr name); @@ -182,10 +184,7 @@ const CxAllocator* allocator, int64_t num); CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, - const char *str); - -CxJsonValue* cxJsonCreateCxString( - const CxAllocator* allocator, cxstring str); + AnyStr str); CxJsonValue* cxJsonCreateLiteral( const CxAllocator* allocator, CxJsonLiteral lit); @@ -208,26 +207,23 @@ int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count); -int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child); +int cxJsonObjPut(CxJsonValue* obj, AnyStr name, CxJsonValue* child); -CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name); +CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, AnyStr name); -CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name); +CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, AnyStr name); CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, - cxstring name, double num); + AnyStr name, double num); CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, - cxstring name, int64_t num); + AnyStr name, int64_t num); CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, - cxstring name, const char* str); - -CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, - cxstring name, cxstring str); + AnyStr name, AnyStr str); CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, - cxstring name, CxJsonLiteral lit); + AnyStr name, CxJsonLiteral lit); ``` The `cxJsonCreateXY()`-family of functions can be used to create JSON values which are allocated by the specified `allocator`. @@ -256,33 +252,33 @@ ```C CxJsonValue *obj = cxJsonCreateObj(NULL); -cxJsonObjPutLiteral(obj, CX_STR("bool"), CX_JSON_FALSE); -cxJsonObjPutInteger(obj, CX_STR("int"), 47); +cxJsonObjPutLiteral(obj, "bool", CX_JSON_FALSE); +cxJsonObjPutInteger(obj, "int", 47); -CxJsonValue *strings = cxJsonObjPutArr(obj, CX_STR("strings")); +CxJsonValue *strings = cxJsonObjPutArr(obj, "strings"); cxJsonArrAddStrings(strings, (const char*[]) {"hello", "world"}, 2); -CxJsonValue *nested = cxJsonObjPutObj(obj, CX_STR("nested")); -CxJsonValue *objects = cxJsonObjPutArr(nested, CX_STR("objects")); +CxJsonValue *nested = cxJsonObjPutObj(obj, "nested"); +CxJsonValue *objects = cxJsonObjPutArr(nested, "objects"); CxJsonValue *obj_in_arr[2] = { cxJsonCreateObj(NULL), cxJsonCreateObj(NULL), }; -cxJsonObjPutInteger(obj_in_arr[0], CX_STR("name1"), 1); -cxJsonObjPutInteger(obj_in_arr[0], CX_STR("name2"), 3); +cxJsonObjPutInteger(obj_in_arr[0], "name1", 1); +cxJsonObjPutInteger(obj_in_arr[0], "name2", 3); -cxJsonObjPutInteger(obj_in_arr[1], CX_STR("name2"), 7); -cxJsonObjPutInteger(obj_in_arr[1], CX_STR("name1"), 3); +cxJsonObjPutInteger(obj_in_arr[1], "name2", 7); +cxJsonObjPutInteger(obj_in_arr[1], "name1", 3); cxJsonArrAddValues(objects, obj_in_arr, 2); -cxJsonArrAddNumbers(cxJsonObjPutArr(nested, CX_STR("floats")), +cxJsonArrAddNumbers(cxJsonObjPutArr(nested, "floats"), (double[]){3.1415, 47.11, 8.15}, 3); -cxJsonArrAddLiterals(cxJsonObjPutArr(nested, CX_STR("literals")), +cxJsonArrAddLiterals(cxJsonObjPutArr(nested, "literals"), (CxJsonLiteral[]){CX_JSON_TRUE, CX_JSON_NULL, CX_JSON_FALSE}, 3); -CxJsonValue *ints = cxJsonObjPutArr(nested, CX_STR("ints")); +CxJsonValue *ints = cxJsonObjPutArr(nested, "ints"); cxJsonArrAddIntegers(ints, (int64_t[]){4, 8, 15}, 3); CxJsonValue *nested_array = cxJsonCreateArr(NULL); diff -r 0dc866c7863b -r 4d641c6a2f82 src/cx/json.h --- a/src/cx/json.h Mon Nov 24 22:39:18 2025 +0100 +++ b/src/cx/json.h Tue Nov 25 20:35:27 2025 +0100 @@ -556,7 +556,7 @@ * @retval non-zero internal allocation error * @see cxJsonFill() */ -cx_attr_nonnull cx_attr_access_r(2, 3) +cx_attr_nonnull_arg(1) cx_attr_access_r(2, 3) CX_EXPORT int cxJsonFilln(CxJson *json, const char *buf, size_t len); @@ -641,28 +641,27 @@ /** * Creates a new JSON string. * + * Internal function - use cxJsonCreateString() instead. + * * @param allocator the allocator to use * @param str the string data * @return the new JSON value or @c NULL if allocation fails - * @see cxJsonCreateString() * @see cxJsonObjPutString() - * @see cxJsonArrAddStrings() + * @see cxJsonArrAddCxStrings() */ -cx_attr_nodiscard cx_attr_nonnull_arg(2) cx_attr_cstr_arg(2) -CX_EXPORT CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str); +cx_attr_nodiscard +CX_EXPORT CxJsonValue* cx_json_create_string(const CxAllocator* allocator, cxstring str); /** * Creates a new JSON string. * - * @param allocator the allocator to use - * @param str the string data - * @return the new JSON value or @c NULL if allocation fails - * @see cxJsonCreateCxString() - * @see cxJsonObjPutCxString() + * @param allocator (@c CxAllocator*) the allocator to use + * @param str the string + * @return (@c CxJsonValue*) the new JSON value or @c NULL if allocation fails + * @see cxJsonObjPutString() * @see cxJsonArrAddCxStrings() */ -cx_attr_nodiscard -CX_EXPORT CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str); +#define cxJsonCreateString(allocator, str) cx_json_create_string(allocator, cx_strcast(str)) /** * Creates a new JSON literal. @@ -760,10 +759,7 @@ /** * Adds or replaces a value within a JSON object. * - * The value will be directly added and not copied. - * - * @note If a value with the specified @p name already exists, - * it will be (recursively) freed with its own allocator. + * Internal function - use cxJsonObjPut(). * * @param obj the JSON object * @param name the name of the value @@ -772,11 +768,29 @@ * @retval non-zero allocation failure */ cx_attr_nonnull -CX_EXPORT int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child); +CX_EXPORT int cx_json_obj_put(CxJsonValue* obj, cxstring name, CxJsonValue* child); + +/** + * Adds or replaces a value within a JSON object. + * + * The value will be directly added and not copied. + * + * @note If a value with the specified @p name already exists, + * it will be (recursively) freed with its own allocator. + * + * @param obj (@c CxJsonValue*) the JSON object + * @param name (any string) the name of the value + * @param child (@c CxJsonValue*) the value + * @retval zero success + * @retval non-zero allocation failure + */ +#define cxJsonObjPut(obj, name, child) cx_json_obj_put(obj, cx_strcast(name), child) /** * Creates a new JSON object and adds it to an existing object. * + * Internal function - use cxJsonObjPutObj(). + * * @param obj the target JSON object * @param name the name of the new value * @return the new value or @c NULL if allocation fails @@ -784,11 +798,24 @@ * @see cxJsonCreateObj() */ cx_attr_nonnull -CX_EXPORT CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name); +CX_EXPORT CxJsonValue* cx_json_obj_put_obj(CxJsonValue* obj, cxstring name); + +/** + * Creates a new JSON object and adds it to an existing object. + * + * @param obj (@c CxJsonValue*) the target JSON object + * @param name (any string) the name of the new value + * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateObj() + */ +#define cxJsonObjPutObj(obj, name) cx_json_obj_put_obj(obj, cx_strcast(name)) /** * Creates a new JSON array and adds it to an object. * + * Internal function - use cxJsonObjPutArr(). + * * @param obj the target JSON object * @param name the name of the new value * @return the new value or @c NULL if allocation fails @@ -796,11 +823,24 @@ * @see cxJsonCreateArr() */ cx_attr_nonnull -CX_EXPORT CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name); +CX_EXPORT CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name); + +/** + * Creates a new JSON array and adds it to an object. + * + * @param obj (@c CxJsonValue*) the target JSON object + * @param name (any string) the name of the new value + * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateArr() + */ +#define cxJsonObjPutArr(obj, name) cx_json_obj_put_arr(obj, cx_strcast(name)) /** * Creates a new JSON number and adds it to an object. * + * Internal function - use cxJsonObjPutNumber(). + * * @param obj the target JSON object * @param name the name of the new value * @param num the numeric value @@ -809,11 +849,25 @@ * @see cxJsonCreateNumber() */ cx_attr_nonnull -CX_EXPORT CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num); +CX_EXPORT CxJsonValue* cx_json_obj_put_number(CxJsonValue* obj, cxstring name, double num); + +/** + * Creates a new JSON number and adds it to an object. + * + * @param obj (@c CxJsonValue*) the target JSON object + * @param name (any string) the name of the new value + * @param num (@c double) the numeric value + * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateNumber() + */ +#define cxJsonObjPutNumber(obj, name, num) cx_json_obj_put_number(obj, cx_strcast(name), num) /** * Creates a new JSON number, based on an integer, and adds it to an object. * + * Internal function - use cxJsonObjPutInteger(). + * * @param obj the target JSON object * @param name the name of the new value * @param num the numeric value @@ -822,12 +876,24 @@ * @see cxJsonCreateInteger() */ cx_attr_nonnull -CX_EXPORT CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num); +CX_EXPORT CxJsonValue* cx_json_obj_put_integer(CxJsonValue* obj, cxstring name, int64_t num); + +/** + * Creates a new JSON number, based on an integer, and adds it to an object. + * + * @param obj (@c CxJsonValue*) the target JSON object + * @param name (any string) the name of the new value + * @param num (@c int64_t) the numeric value + * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateInteger() + */ +#define cxJsonObjPutInteger(obj, name, num) cx_json_obj_put_integer(obj, cx_strcast(name), num) /** * Creates a new JSON string and adds it to an object. * - * The string data is copied. + * Internal function - use cxJsonObjPutString() * * @param obj the target JSON object * @param name the name of the new value @@ -836,27 +902,28 @@ * @see cxJsonObjPut() * @see cxJsonCreateString() */ -cx_attr_nonnull cx_attr_cstr_arg(3) -CX_EXPORT CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str); +cx_attr_nonnull +CX_EXPORT CxJsonValue* cx_json_obj_put_string(CxJsonValue* obj, cxstring name, cxstring str); /** * Creates a new JSON string and adds it to an object. * * The string data is copied. * - * @param obj the target JSON object - * @param name the name of the new value - * @param str the string data - * @return the new value or @c NULL if allocation fails + * @param obj (@c CxJsonValue*) the target JSON object + * @param name (any string) the name of the new value + * @param str (any string) the string data + * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails * @see cxJsonObjPut() - * @see cxJsonCreateCxString() + * @see cxJsonCreateString() */ -cx_attr_nonnull -CX_EXPORT CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str); +#define cxJsonObjPutString(obj, name, str) cx_json_obj_put_string(obj, cx_strcast(name), cx_strcast(str)) /** * Creates a new JSON literal and adds it to an object. * + * Internal function - use cxJsonObjPutLiteral(). + * * @param obj the target JSON object * @param name the name of the new value * @param lit the type of literal @@ -865,7 +932,19 @@ * @see cxJsonCreateLiteral() */ cx_attr_nonnull -CX_EXPORT CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit); +CX_EXPORT CxJsonValue* cx_json_obj_put_literal(CxJsonValue* obj, cxstring name, CxJsonLiteral lit); + +/** + * Creates a new JSON literal and adds it to an object. + * + * @param obj (@c CxJsonValue*) the target JSON object + * @param name (any string) the name of the new value + * @param lit (@c CxJsonLiteral) the type of literal + * @return (@c CxJsonValue*) the new value or @c NULL if allocation fails + * @see cxJsonObjPut() + * @see cxJsonCreateLiteral() + */ +#define cxJsonObjPutLiteral(obj, name, lit) cx_json_obj_put_literal(obj, cx_strcast(name), lit) /** * Recursively deallocates the memory of a JSON value. @@ -1188,6 +1267,20 @@ CX_EXPORT CxIterator cxJsonArrIter(const CxJsonValue *value); /** + * Returns the size of a JSON object. + * + * If the @p value is not a JSON object, the behavior is undefined. + * + * @param value the JSON value + * @return the size of the object, i.e., the number of key/value pairs + * @see cxJsonIsObject() + */ +cx_attr_nonnull +CX_INLINE size_t cxJsonObjSize(const CxJsonValue *value) { + return value->value.object.values_size; +} + +/** * Returns an iterator over the JSON object members. * * The iterator yields values of type @c CxJsonObjValue* which diff -r 0dc866c7863b -r 4d641c6a2f82 src/json.c --- a/src/json.c Mon Nov 24 22:39:18 2025 +0100 +++ b/src/json.c Tue Nov 25 20:35:27 2025 +0100 @@ -94,7 +94,7 @@ size_t newcap = obj->values_capacity; if (newcap > oldcap) { if (cxReallocateArray(al, &obj->indices, newcap, sizeof(size_t))) { - return 1; + return 1; // LCOV_EXCL_LINE } } @@ -437,7 +437,7 @@ } else if (c == 'u') { char utf8buf[4]; unsigned utf8len = unescape_unicode_string( - cx_strn(str.ptr + i - 1, str.length + 1 - i), + cx_strn(str.ptr + i - 1, str.length - i), utf8buf ); if(utf8len > 0) { @@ -456,7 +456,7 @@ } else { // TODO: discuss the behavior for unrecognized escape sequences // most parsers throw an error here - we just ignore it - result.ptr[result.length++] = '\\'; + result.ptr[result.length++] = '\\'; // LCOV_EXCL_LINE } result.ptr[result.length++] = c; @@ -640,6 +640,7 @@ if (cxBufferEof(&json->buffer)) { // reinitialize the buffer cxBufferDestroy(&json->buffer); + if (buf == NULL) buf = ""; // buffer must not be initialized with NULL cxBufferInit(&json->buffer, (char*) buf, size, NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE); json->buffer.size = size; @@ -734,7 +735,8 @@ } } else { if (cx_strtod(token.content, &vbuf->value.number)) { - return_rec(CX_JSON_FORMAT_ERROR_NUMBER); + // TODO: at the moment this is unreachable, because the tokenizer is already stricter than cx_strtod() + return_rec(CX_JSON_FORMAT_ERROR_NUMBER); // LCOV_EXCL_LINE } } return_rec(CX_JSON_NO_ERROR); @@ -815,19 +817,19 @@ } else { // should be unreachable assert(false); - return_rec(-1); + return_rec(-1); // LCOV_EXCL_LINE } } CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) { - // check if buffer has been filled + // initialize output value + *value = &cx_json_value_nothing; + + // check if the buffer has been filled if (json->buffer.space == NULL) { return CX_JSON_NULL_DATA; } - // initialize output value - *value = &cx_json_value_nothing; - // parse data CxJsonStatus result; do { @@ -943,11 +945,7 @@ return v; } -CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char* str) { - return cxJsonCreateCxString(allocator, cx_str(str)); -} - -CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str) { +CxJsonValue* cx_json_create_string(const CxAllocator* allocator, cxstring str) { if (allocator == NULL) allocator = cxDefaultAllocator; CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue)); if (v == NULL) return NULL; @@ -1020,7 +1018,7 @@ CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*)); if (values == NULL) return -1; for (size_t i = 0; i < count; i++) { - values[i] = cxJsonCreateCxString(arr->allocator, str[i]); + values[i] = cxJsonCreateString(arr->allocator, str[i]); if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } } int ret = cxJsonArrAddValues(arr, values, count); @@ -1050,61 +1048,56 @@ ); } -int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child) { +int cx_json_obj_put(CxJsonValue* obj, cxstring name, CxJsonValue* child) { cxmutstr k = cx_strdup_a(obj->allocator, name); if (k.ptr == NULL) return -1; CxJsonObjValue kv = {k, child}; if (json_add_objvalue(obj, kv)) { + // LCOV_EXCL_START cx_strfree_a(obj->allocator, &k); return 1; + // LCOV_EXCL_STOP } else { return 0; } } -CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name) { +CxJsonValue* cx_json_obj_put_obj(CxJsonValue* obj, cxstring name) { CxJsonValue* v = cxJsonCreateObj(obj->allocator); if (v == NULL) return NULL; if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } return v; } -CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name) { +CxJsonValue* cx_json_obj_put_arr(CxJsonValue* obj, cxstring name) { CxJsonValue* v = cxJsonCreateArr(obj->allocator); if (v == NULL) return NULL; if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } return v; } -CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num) { +CxJsonValue* cx_json_obj_put_number(CxJsonValue* obj, cxstring name, double num) { CxJsonValue* v = cxJsonCreateNumber(obj->allocator, num); if (v == NULL) return NULL; if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } return v; } -CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num) { +CxJsonValue* cx_json_obj_put_integer(CxJsonValue* obj, cxstring name, int64_t num) { CxJsonValue* v = cxJsonCreateInteger(obj->allocator, num); if (v == NULL) return NULL; if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } return v; } -CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str) { +CxJsonValue* cx_json_obj_put_string(CxJsonValue* obj, cxstring name, cxstring str) { CxJsonValue* v = cxJsonCreateString(obj->allocator, str); if (v == NULL) return NULL; if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } return v; } -CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str) { - CxJsonValue* v = cxJsonCreateCxString(obj->allocator, str); - if (v == NULL) return NULL; - if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; } - return v; -} - -CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) { +CxJsonValue* cx_json_obj_put_literal(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) { CxJsonValue* v = cxJsonCreateLiteral(obj->allocator, lit); if (v == NULL) return NULL; if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL;} @@ -1286,9 +1279,6 @@ ? look_idx : value->value.object.indices[look_idx]; CxJsonObjValue *member = &value->value.object.values[elem_idx]; - if (settings->sort_members) { - depth++;depth--; - } // possible indentation if (settings->pretty) { @@ -1350,7 +1340,9 @@ if (cx_json_write_rec( target, element, wfunc, settings, depth) - ) return 1; + ) { + return 1; // LCOV_EXCL_LINE + } if (iter.index < iter.elem_count - 1) { const char *arr_value_sep = ", "; diff -r 0dc866c7863b -r 4d641c6a2f82 tests/test_json.c --- a/tests/test_json.c Mon Nov 24 22:39:18 2025 +0100 +++ b/tests/test_json.c Tue Nov 25 20:35:27 2025 +0100 @@ -78,7 +78,7 @@ CX_TEST_ASSERT(cxJsonIsString(message)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(message), - cx_str("success")) + "success") ); CxJsonValue *position = cxJsonObjGet(obj, "position"); @@ -117,6 +117,32 @@ } } +CX_TEST(test_json_large_object) { + CxJsonValue *obj = cxJsonCreateObj(NULL); + CX_TEST_DO { + cxJsonObjPutString(obj, "mystring", "test"); + char buf[10]; + for (unsigned i = 0 ; i < 300 ; i++) { + sprintf(buf, "key %d", i); + cxJsonObjPutInteger(obj, buf, i); + } + CX_TEST_ASSERT(301 == cxJsonObjSize(obj)); + // some samples + CxJsonValue *v; + v = cxJsonObjGet(obj, "key 64"); + CX_TEST_ASSERT(cxJsonIsInteger(v)); + CX_TEST_ASSERT(cxJsonAsInteger(v) == 64); + v = cxJsonObjGet(obj, "key 228"); + CX_TEST_ASSERT(cxJsonIsInteger(v)); + CX_TEST_ASSERT(cxJsonAsInteger(v) == 228); + + v = cxJsonObjGet(obj, "mystring"); + CX_TEST_ASSERT(cxJsonIsString(v)); + CX_TEST_ASSERT(0 == cx_strcmp(cxJsonAsCxMutStr(v), "test")); + } + cxJsonValueFree(obj); +} + CX_TEST(test_json_escaped_strings) { cxstring text = cx_str( "{\n" @@ -137,13 +163,13 @@ CX_TEST_ASSERT(cxJsonIsString(object)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(object), - CX_STR("{\n\t\"object\":null\n}")) + "{\n\t\"object\":null\n}") ); CxJsonValue *ctrl = cxJsonObjGet(obj, "ctrl-chars"); CX_TEST_ASSERT(cxJsonIsString(ctrl)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(ctrl), - CX_STR("\\foo\r\nbar\f*ring/ring*\b")) + "\\foo\r\nbar\f*ring/ring*\b") ); cxJsonValueFree(obj); } @@ -176,39 +202,39 @@ CX_TEST_ASSERT(cxJsonIsString(ascii)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(ascii), - CX_STR("ASCII")) + "ASCII") ); CxJsonValue *unicode = cxJsonObjGet(obj, "unicode"); CX_TEST_ASSERT(cxJsonIsString(unicode)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(unicode), - CX_STR("ßß")) + "ßß") ); CxJsonValue *mixed = cxJsonObjGet(obj, "mixed"); CX_TEST_ASSERT(cxJsonIsString(mixed)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(mixed), - CX_STR("mixed ä ö ä ö")) + "mixed ä ö ä ö") ); CxJsonValue *wide = cxJsonObjGet(obj, "wide"); CX_TEST_ASSERT(cxJsonIsString(wide)); - CX_TEST_ASSERT(0 == cx_strcmp(cxJsonAsCxString(wide), CX_STR("Σ⦰"))); + CX_TEST_ASSERT(0 == cx_strcmp(cxJsonAsCxString(wide), "Σ⦰")); CxJsonValue *surrogatepair1 = cxJsonObjGet(obj, "surrogatepair1"); CX_TEST_ASSERT(cxJsonIsString(surrogatepair1)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(surrogatepair1), - CX_STR("\xf0\x9f\xaf\xb5")) + "\xf0\x9f\xaf\xb5") ); CxJsonValue *surrogatepair2 = cxJsonObjGet(obj, "surrogatepair2"); CX_TEST_ASSERT(cxJsonIsString(surrogatepair2)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(surrogatepair2), - CX_STR("test\xf0\x9f\xaf\xb1" "AA")) + "test\xf0\x9f\xaf\xb1" "AA") ); CxJsonValue *mixed2 = cxJsonObjGet(obj, "mixed2"); @@ -217,7 +243,7 @@ CX_TEST_ASSERT(cxJsonIsString(mixed2)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(mixed2), - CX_STR("123\xce\xa3\xf0\x9f\xaf\x85ß")) + "123\xce\xa3\xf0\x9f\xaf\x85ß") ); cxJsonValueFree(obj); @@ -231,22 +257,22 @@ CxJsonValue *obj; CxJsonStatus result; CX_TEST_DO { - cxJsonFill(&json, "\"too few \\u123 digits\""); + cxJsonFill(&json, "\"too few digits \\u123\""); result = cxJsonNext(&json, &obj); CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); CX_TEST_ASSERT(cxJsonIsString(obj)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(obj), - CX_STR("too few \\u123 digits") + "too few digits \\u123" )); cxJsonValueFree(obj); - cxJsonFill(&json, "\"too many \\u00E456 digits\""); + cxJsonFill(&json, "\"too many digits \\u00E456\""); result = cxJsonNext(&json, &obj); CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); CX_TEST_ASSERT(cxJsonIsString(obj)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(obj), - CX_STR("too many ä56 digits") + "too many digits ä56" )); cxJsonValueFree(obj); cxJsonFill(&json, "\"only high \\uD800 surrogate\""); @@ -255,7 +281,7 @@ CX_TEST_ASSERT(cxJsonIsString(obj)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(obj), - CX_STR("only high \\uD800 surrogate") + "only high \\uD800 surrogate" )); cxJsonValueFree(obj); cxJsonFill(&json, "\"only low \\uDC00 surrogate\""); @@ -264,7 +290,7 @@ CX_TEST_ASSERT(cxJsonIsString(obj)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(obj), - CX_STR("only low \\uDC00 surrogate") + "only low \\uDC00 surrogate" )); cxJsonValueFree(obj); cxJsonFill(&json, "\"two high \\uD800\\uD800 surrogates\""); @@ -273,7 +299,7 @@ CX_TEST_ASSERT(cxJsonIsString(obj)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(obj), - CX_STR("two high \\uD800\\uD800 surrogates") + "two high \\uD800\\uD800 surrogates" )); cxJsonValueFree(obj); cxJsonFill(&json, "\"high plus bullshit \\uD800\\u567 foo\""); @@ -282,7 +308,7 @@ CX_TEST_ASSERT(cxJsonIsString(obj)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(obj), - CX_STR("high plus bullshit \\uD800\\u567 foo") + "high plus bullshit \\uD800\\u567 foo" )); cxJsonValueFree(obj); } @@ -301,7 +327,7 @@ CX_TEST_ASSERT(cxJsonIsString(val)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(val), - cx_str("a \"test\" string")) + "a \"test\" string") ); cxJsonValueFree(val); @@ -315,7 +341,7 @@ CX_TEST_ASSERT(cxJsonIsString(val)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(val), - cx_str("a \"test\" string")) + "a \"test\" string") ); cxJsonValueFree(val); } @@ -354,7 +380,7 @@ CX_TEST_ASSERT(cxJsonIsString(message)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(message), - cx_str("success")) + "success") ); CxJsonValue *timestamp = cxJsonObjGet(obj, "__timestamp"); CX_TEST_ASSERT(message->type == CX_JSON_STRING); @@ -402,6 +428,55 @@ cx_testing_allocator_destroy(&talloc); } +CX_TEST(test_json_parenthesis_mismatch) { + CxTestingAllocator talloc; + cx_testing_allocator_init(&talloc); + const CxAllocator *alloc = &talloc.base; + + CX_TEST_DO { + CxJson json; + cxJsonInit(&json, alloc); + + CxJsonStatus result; + CxJsonValue *obj; + + cxJsonFill(&json, "[0, 1, 2, 3}"); + result = cxJsonNext(&json, &obj); + CX_TEST_ASSERT(result == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + + cxJsonReset(&json); + cxJsonFill(&json, "{\"test\": 42]"); + result = cxJsonNext(&json, &obj); + CX_TEST_ASSERT(result == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + + cxJsonDestroy(&json); + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + } + cx_testing_allocator_destroy(&talloc); +} + +CX_TEST(test_json_object_name_is_no_string) { + CxTestingAllocator talloc; + cx_testing_allocator_init(&talloc); + const CxAllocator *alloc = &talloc.base; + + CX_TEST_DO { + CxJson json; + cxJsonInit(&json, alloc); + + CxJsonStatus result; + CxJsonValue *obj; + + cxJsonFill(&json, "{42: \"test\"}"); + result = cxJsonNext(&json, &obj); + CX_TEST_ASSERT(result == CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN); + + cxJsonDestroy(&json); + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + } + cx_testing_allocator_destroy(&talloc); +} + CX_TEST(test_json_subsequent_fill) { cxstring text = cx_str( "{\"message\":\"success\" , \"__timestamp\":1729348561}"); @@ -424,7 +499,7 @@ CX_TEST_ASSERT(cxJsonIsString(message)); CX_TEST_ASSERT(0 == cx_strcmp( cxJsonAsCxString(message), - cx_str("success")) + "success") ); CxJsonValue *timestamp = cxJsonObjGet(obj, "__timestamp"); CX_TEST_ASSERT(message->type == CX_JSON_STRING); @@ -439,6 +514,63 @@ } } +CX_TEST(test_json_no_fill) { + CxJson json; + cxJsonInit(&json, NULL); + CX_TEST_DO { + CxJsonValue *obj = NULL; + CxJsonStatus result = cxJsonNext(&json, &obj); + CX_TEST_ASSERT(result == CX_JSON_NULL_DATA); + CX_TEST_ASSERT(obj != NULL); + CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); + } + cxJsonDestroy(&json); +} + +CX_TEST(test_json_null_fill) { + CxJson json; + cxJsonInit(&json, NULL); + CX_TEST_DO { + CxJsonStatus result; + CxJsonValue *obj = NULL; + cxstring nullstr = cx_strn(NULL, 0); + cxJsonFill(&json, nullstr); + result = cxJsonNext(&json, &obj); + CX_TEST_ASSERT(result == CX_JSON_NO_DATA); + CX_TEST_ASSERT(obj != NULL); + CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); + obj = NULL; + + cxJsonFill(&json, "[0, 1"); + result = cxJsonNext(&json, &obj); + CX_TEST_ASSERT(result == CX_JSON_INCOMPLETE_DATA); + CX_TEST_ASSERT(obj != NULL); + CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); + obj = NULL; + + cxJsonFill(&json, nullstr); + result = cxJsonNext(&json, &obj); + CX_TEST_ASSERT(result == CX_JSON_INCOMPLETE_DATA); + CX_TEST_ASSERT(obj != NULL); + CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); + obj = NULL; + + cxJsonFill(&json, ", 2]"); + result = cxJsonNext(&json, &obj); + CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); + CX_TEST_ASSERT(obj != NULL); + CX_TEST_ASSERT(cxJsonIsArray(obj)); + cxJsonValueFree(obj); + + cxJsonFill(&json, nullstr); + result = cxJsonNext(&json, &obj); + CX_TEST_ASSERT(result == CX_JSON_NO_DATA); + CX_TEST_ASSERT(obj != NULL); + CX_TEST_ASSERT(obj->type == CX_JSON_NOTHING); + } + cxJsonDestroy(&json); +} + CX_TEST(test_json_object_error) { cxstring text0 = cx_str( "{\n" @@ -489,7 +621,7 @@ CX_TEST_DO { CxJson json; cxJsonInit(&json, alloc); - cxJsonFill(&json, cx_str( + cxJsonFill(&json, "{\n" "\t\"message\":\"success\",\n" "\t\"data\":{\n" @@ -499,7 +631,7 @@ "\t},\n" "\t\"timestamp\":1729348561\n" "}" - )); + ); CxJsonValue *obj; CX_TEST_ASSERT(CX_JSON_NO_ERROR == cxJsonNext(&json, &obj)); cxJsonDestroy(&json); @@ -704,7 +836,7 @@ result = cxJsonNext(&json, &v); CX_TEST_ASSERT(result == CX_JSON_NO_ERROR); CX_TEST_ASSERT(cxJsonIsString(v)); - CX_TEST_ASSERT(!cx_strcmp(cxJsonAsCxString(v), CX_STR("hello world"))); + CX_TEST_ASSERT(!cx_strcmp(cxJsonAsCxString(v), "hello world")); cxJsonValueFree(v); // don't process the remaining newline this time // read obj @@ -904,24 +1036,24 @@ // add the members { - cxJsonObjPutLiteral(obj, CX_STR("bool"), CX_JSON_FALSE); - cxJsonObjPutInteger(obj, CX_STR("int"), 47); - CxJsonValue *strings = cxJsonObjPutArr(obj, CX_STR("strings")); + cxJsonObjPutLiteral(obj, "bool", CX_JSON_FALSE); + cxJsonObjPutInteger(obj, "int", 47); + CxJsonValue *strings = cxJsonObjPutArr(obj, "strings"); CX_TEST_ASSERT(strings != NULL); CX_TEST_ASSERT(cxJsonIsArray(strings)); const char* str[] = {"hello", "world"}; CX_TEST_ASSERT(0 == cxJsonArrAddStrings(strings, str, 2)); - CxJsonValue *nested = cxJsonObjPutObj(obj, CX_STR("nested")); + CxJsonValue *nested = cxJsonObjPutObj(obj, "nested"); CX_TEST_ASSERT(nested != NULL); CX_TEST_ASSERT(cxJsonIsObject(nested)); - cxJsonObjPutCxString(nested, CX_STR("string"), CX_STR("test")); + cxJsonObjPutString(nested, "string", "test"); - cxJsonArrAddNumbers(cxJsonObjPutArr(nested, CX_STR("floats")), + cxJsonArrAddNumbers(cxJsonObjPutArr(nested, "floats"), (double[]){3.1415, 47.11, 8.15}, 3); - cxJsonArrAddIntegers(cxJsonObjPutArr(nested, CX_STR("ints")), + cxJsonArrAddIntegers(cxJsonObjPutArr(nested, "ints"), (int64_t[]){4, 8, 15, 16, 23, 42}, 6); - cxJsonArrAddLiterals(cxJsonObjPutArr(nested, CX_STR("literals")), + cxJsonArrAddLiterals(cxJsonObjPutArr(nested, "literals"), (CxJsonLiteral[]){CX_JSON_TRUE, CX_JSON_NULL, CX_JSON_FALSE}, 3); } @@ -932,8 +1064,8 @@ CxJsonValue *strings = cxJsonObjGet(obj, "strings"); CX_TEST_ASSERT(cxJsonIsArray(strings)); CX_TEST_ASSERT(2 == cxJsonArrSize(strings)); - CX_TEST_ASSERT(0 == cx_strcmp(CX_STR("hello"), cxJsonAsCxString(cxJsonArrGet(strings, 0)))); - CX_TEST_ASSERT(0 == cx_strcmp(CX_STR("world"), cxJsonAsCxString(cxJsonArrGet(strings, 1)))); + CX_TEST_ASSERT(0 == cx_strcmp("hello", cxJsonAsString(cxJsonArrGet(strings, 0)))); + CX_TEST_ASSERT(0 == cx_strcmp("world", cxJsonAsString(cxJsonArrGet(strings, 1)))); CxJsonValue *nested = cxJsonObjGet(obj, "nested"); CX_TEST_ASSERT(cxJsonIsObject(nested)); @@ -968,6 +1100,43 @@ cx_testing_allocator_destroy(&talloc); } +CX_TEST(test_json_overwrite_value) { + CxTestingAllocator talloc; + cx_testing_allocator_init(&talloc); + CxAllocator *allocator = &talloc.base; + CX_TEST_DO { + CxJsonValue *obj = cxJsonCreateObj(allocator); + + // put some values + cxJsonObjPutInteger(obj, "test1", 1); + cxJsonObjPutInteger(obj, "test2", 2); + cxJsonObjPutInteger(obj, "test3", 3); + + // overwrite test2 + cxJsonObjPutInteger(obj, "test2", 0); + + // verify the values + CxIterator iter = cxJsonObjIter(obj); + bool found[5] = {0}; + cx_foreach(CxJsonObjValue *, ov, iter) { + CxJsonValue *v = ov->value; + CX_TEST_ASSERT(cxJsonIsInteger(v)); + int64_t i = cxJsonAsInteger(v); + CX_TEST_ASSERT(i >= 0 && i <= 4); + found[i] = true; + } + CX_TEST_ASSERT(found[0]); + CX_TEST_ASSERT(found[1]); + CX_TEST_ASSERT(!found[2]); + CX_TEST_ASSERT(found[3]); + CX_TEST_ASSERT(!found[4]); + + // destroy the value and verify the allocations + cxJsonValueFree(obj); + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + } + cx_testing_allocator_destroy(&talloc); +} CX_TEST_SUBROUTINE(test_json_write_sub, const CxAllocator *allocator, @@ -976,23 +1145,23 @@ ) { // create the value CxJsonValue *obj = cxJsonCreateObj(allocator); - cxJsonObjPutLiteral(obj, CX_STR("bool"), CX_JSON_FALSE); - cxJsonObjPutNumber(obj, CX_STR("int"), 47); // purposely use PutNumber to put an int - CxJsonValue *strings = cxJsonObjPutArr(obj, CX_STR("strings")); + cxJsonObjPutLiteral(obj, "bool", CX_JSON_FALSE); + cxJsonObjPutNumber(obj, "int", 47); // purposely use PutNumber to put an int + CxJsonValue *strings = cxJsonObjPutArr(obj, "strings"); cxJsonArrAddCxStrings(strings, (cxstring[]) {CX_STR("hello"), CX_STR("world")}, 2); - CxJsonValue *nested = cxJsonObjPutObj(obj, CX_STR("nested")); - CxJsonValue *objects = cxJsonObjPutArr(nested, CX_STR("objects")); + CxJsonValue *nested = cxJsonObjPutObj(obj, "nested"); + CxJsonValue *objects = cxJsonObjPutArr(nested, "objects"); CxJsonValue *obj_in_arr[2] = {cxJsonCreateObj(allocator), cxJsonCreateObj(allocator)}; - cxJsonObjPutInteger(obj_in_arr[0], CX_STR("name1"), 1); - cxJsonObjPutInteger(obj_in_arr[0], CX_STR("name2"), 3); - cxJsonObjPutInteger(obj_in_arr[1], CX_STR("name2"), 7); - cxJsonObjPutInteger(obj_in_arr[1], CX_STR("name1"), 3); + cxJsonObjPutInteger(obj_in_arr[0], "name1", 1); + cxJsonObjPutInteger(obj_in_arr[0], "name2", 3); + cxJsonObjPutInteger(obj_in_arr[1], "name2", 7); + cxJsonObjPutInteger(obj_in_arr[1], "name1", 3); cxJsonArrAddValues(objects, obj_in_arr, 2); - cxJsonArrAddNumbers(cxJsonObjPutArr(nested, CX_STR("floats")), + cxJsonArrAddNumbers(cxJsonObjPutArr(nested, "floats"), (double[]){3.1415, 47.11, 8.15}, 3); - cxJsonArrAddLiterals(cxJsonObjPutArr(nested, CX_STR("literals")), + cxJsonArrAddLiterals(cxJsonObjPutArr(nested, "literals"), (CxJsonLiteral[]){CX_JSON_TRUE, CX_JSON_NULL, CX_JSON_FALSE}, 3); - CxJsonValue *ints = cxJsonObjPutArr(nested, CX_STR("ints")); + CxJsonValue *ints = cxJsonObjPutArr(nested, "ints"); cxJsonArrAddIntegers(ints, (int64_t[]){4, 8, 15}, 3); CxJsonValue *nested_array = cxJsonCreateArr(allocator); cxJsonArrAddValues(ints, &nested_array, 1); @@ -1020,7 +1189,7 @@ CxAllocator *allocator = &talloc.base; CX_TEST_DO { // expected value - cxstring expected = CX_STR( + cxstring expected = cx_str( "{\"bool\":false," "\"int\":47," "\"nested\":{" @@ -1041,6 +1210,8 @@ CxJsonWriter writer = cxJsonWriterCompact(); CX_TEST_CALL_SUBROUTINE(test_json_write_sub, allocator, expected, &writer); + // try again, but this time with implicitly defaulted writer + CX_TEST_CALL_SUBROUTINE(test_json_write_sub, allocator, expected, NULL); CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); } cx_testing_allocator_destroy(&talloc); @@ -1051,7 +1222,7 @@ cx_testing_allocator_init(&talloc); CxAllocator *allocator = &talloc.base; CX_TEST_DO { - cxstring expected = CX_STR( + cxstring expected = cx_str( "{\n" " \"bool\": false,\n" " \"int\": 47,\n" @@ -1083,7 +1254,7 @@ cx_testing_allocator_init(&talloc); CxAllocator *allocator = &talloc.base; CX_TEST_DO { - cxstring expected = CX_STR( + cxstring expected = cx_str( "{\n" "\t\"bool\": false,\n" "\t\"int\": 47,\n" @@ -1114,7 +1285,7 @@ cx_testing_allocator_init(&talloc); CxAllocator *allocator = &talloc.base; CX_TEST_DO { - cxstring expected = CX_STR( + cxstring expected = cx_str( "{\n" " \"bool\": false,\n" " \"int\": 47,\n" @@ -1142,6 +1313,54 @@ cx_testing_allocator_destroy(&talloc); } +CX_TEST(test_json_write_pretty_deep_nesting) { + CxTestingAllocator talloc; + cx_testing_allocator_init(&talloc); + CxAllocator *allocator = &talloc.base; + CX_TEST_DO { + cxstring expected = cx_str( +"{\n" +" \"test\": {\n" +" \"test\": {\n" +" \"test\": {\n" +" \"test\": {\n" +" \"test\": {\n" +" \"test\": 47\n" +" }\n" +" }\n" +" }\n" +" }\n" +" }\n" +"}" + ); + + CxJsonValue *obj = cxJsonCreateObj(allocator); + CxJsonValue *test = obj; + for (unsigned i = 0 ; i < 5 ; i++) { + test = cxJsonObjPutObj(test, "test"); + } + cxJsonObjPutInteger(test, "test", 47); + + CxJsonWriter writer = cxJsonWriterPretty(true); + writer.indent = 8; + CxBuffer buf; + cxBufferInit(&buf, NULL, 512, NULL, CX_BUFFER_DEFAULT); + int result = cxJsonWrite(&buf, obj, cxBufferWriteFunc, &writer); + cxBufferTerminate(&buf); // makes debugging easier + CX_TEST_ASSERT(result == 0); + + // compare the string + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), expected)); + + // destroy everything + cxBufferDestroy(&buf); + cxJsonValueFree(obj); + + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + } + cx_testing_allocator_destroy(&talloc); +} + CX_TEST(test_json_write_frac_max_digits) { CxJsonValue* num = cxJsonCreateNumber(NULL, 3.141592653589793); CxJsonWriter writer = cxJsonWriterCompact(); @@ -1150,45 +1369,45 @@ CX_TEST_DO { // test default settings (6 digits) cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer); - CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("3.141592"))); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "3.141592")); // test too many digits cxBufferReset(&buf); writer.frac_max_digits = 200; cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer); - CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("3.141592653589793"))); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "3.141592653589793")); // test 0 digits cxBufferReset(&buf); writer.frac_max_digits = 0; cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer); - CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("3"))); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "3")); // test 2 digits cxBufferReset(&buf); writer.frac_max_digits = 2; cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer); - CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("3.14"))); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "3.14")); // test 3 digits cxBufferReset(&buf); writer.frac_max_digits = 3; cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer); - CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("3.141"))); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "3.141")); // test 6 digits, but two are left of the decimal point num->value.number = 47.110815; cxBufferReset(&buf); writer.frac_max_digits = 6; cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer); - CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("47.110815"))); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "47.110815")); // test 4 digits with exponent num->value.number = 5.11223344e23; cxBufferReset(&buf); writer.frac_max_digits = 4; cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer); - CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("5.1122e+23"))); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "5.1122e+23")); } cxBufferDestroy(&buf); cxJsonValueFree(num); @@ -1216,7 +1435,7 @@ CX_TEST_DO { cxJsonWrite(&buf, str, cxBufferWriteFunc, &writer); CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), - CX_STR("\"hello\\twörld\\r\\nthis is\\\\a \\\"string\\\"\\b in \\u0007 string\\f\""))); + "\"hello\\twörld\\r\\nthis is\\\\a \\\"string\\\"\\b in \\u0007 string\\f\"")); } cxBufferDestroy(&buf); cxJsonValueFree(str); @@ -1225,14 +1444,14 @@ CX_TEST(test_json_write_name_escape) { CxJsonValue* obj = cxJsonCreateObj(NULL); cxJsonObjPutLiteral(obj, - CX_STR("hello\twörld\r\nthis is\\a \"string\"\b in \a string\f"), CX_JSON_TRUE); + "hello\twörld\r\nthis is\\a \"string\"\b in \a string\f", CX_JSON_TRUE); CxJsonWriter writer = cxJsonWriterCompact(); CxBuffer buf; cxBufferInit(&buf, NULL, 128, NULL, 0); CX_TEST_DO { cxJsonWrite(&buf, obj, cxBufferWriteFunc, &writer); CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), - CX_STR("{\"hello\\twörld\\r\\nthis is\\\\a \\\"string\\\"\\b in \\u0007 string\\f\":true}"))); + "{\"hello\\twörld\\r\\nthis is\\\\a \\\"string\\\"\\b in \\u0007 string\\f\":true}")); } cxBufferDestroy(&buf); cxJsonValueFree(obj); @@ -1246,32 +1465,49 @@ CX_TEST_DO { // default: do not escape cxJsonWrite(&buf, str, cxBufferWriteFunc, &writer); - CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("\"test/solidus\""))); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "\"test/solidus\"")); // enable escaping writer.escape_slash = true; cxBufferReset(&buf); cxJsonWrite(&buf, str, cxBufferWriteFunc, &writer); - CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("\"test\\/solidus\""))); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), "\"test\\/solidus\"")); } cxBufferDestroy(&buf); cxJsonValueFree(str); } +CX_TEST(test_json_write_nothing) { + CxBuffer buf; + cxBufferInit(&buf, NULL, 16, NULL, 0); + CX_TEST_DO { + CxJsonValue nothing; + nothing.type = CX_JSON_NOTHING; + cxJsonWrite(&buf, ¬hing, cxBufferWriteFunc, NULL); + CX_TEST_ASSERT(buf.size == 0); + } + cxBufferDestroy(&buf); +} + CxTestSuite *cx_test_suite_json(void) { CxTestSuite *suite = cx_test_suite_new("json"); cx_test_register(suite, test_json_init_default); cx_test_register(suite, test_json_simple_object); + cx_test_register(suite, test_json_large_object); cx_test_register(suite, test_json_escaped_strings); cx_test_register(suite, test_json_escaped_unicode_strings); cx_test_register(suite, test_json_escaped_unicode_malformed); cx_test_register(suite, test_json_escaped_end_of_string); cx_test_register(suite, test_json_object_incomplete_token); + cx_test_register(suite, test_json_parenthesis_mismatch); + cx_test_register(suite, test_json_object_name_is_no_string); cx_test_register(suite, test_json_token_wrongly_completed); cx_test_register(suite, test_json_object_error); cx_test_register(suite, test_json_object_remove_member); cx_test_register(suite, test_json_subsequent_fill); + cx_test_register(suite, test_json_no_fill); + cx_test_register(suite, test_json_null_fill); cx_test_register(suite, test_json_large_nesting_depth); cx_test_register(suite, test_json_number); cx_test_register(suite, test_json_number_format_errors); @@ -1281,14 +1517,17 @@ cx_test_register(suite, test_json_allocator); cx_test_register(suite, test_json_allocator_parse_error); cx_test_register(suite, test_json_create_value); + cx_test_register(suite, test_json_overwrite_value); cx_test_register(suite, test_json_write_default_format); cx_test_register(suite, test_json_write_pretty_default_spaces); cx_test_register(suite, test_json_write_pretty_default_tabs); cx_test_register(suite, test_json_write_pretty_preserve_order); + cx_test_register(suite, test_json_write_pretty_deep_nesting); cx_test_register(suite, test_json_write_frac_max_digits); cx_test_register(suite, test_json_write_string_escape); cx_test_register(suite, test_json_write_name_escape); cx_test_register(suite, test_json_write_solidus); + cx_test_register(suite, test_json_write_nothing); return suite; }