# HG changeset patch # User Mike Becker # Date 1755272521 -7200 # Node ID cc153bffea28ae36db261665296d9b4583af83d7 # Parent 6dfa1eb58ce30f000fb1fe6a0f262a4e12de8430 add cxJsonObjRemove() #627 diff -r 6dfa1eb58ce3 -r cc153bffea28 CHANGELOG --- a/CHANGELOG Thu Aug 14 23:03:01 2025 +0200 +++ b/CHANGELOG Fri Aug 15 17:42:01 2025 +0200 @@ -13,7 +13,7 @@ * adds cxTreeSize() * adds CX_PRIstr and CX_SFMT macros for formatting UCX strings * adds cx_strcpy() and cx_strcpy_a() - * adds cxJsonArrRemove() + * adds cxJsonArrRemove() and cxJsonObjRemove() * adds cxStdlibAllocator and allows changes of cxDefaultAllocator * improves performance of the CxList array list implementation * changes cx_str() and cx_mutstr() to allow NULL strings diff -r 6dfa1eb58ce3 -r cc153bffea28 src/cx/json.h --- a/src/cx/json.h Thu Aug 14 23:03:01 2025 +0200 +++ b/src/cx/json.h Fri Aug 15 17:42:01 2025 +0200 @@ -1341,6 +1341,13 @@ cx_attr_export CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name); +/** + * @copydoc cxJsonObjRemove() + */ +cx_attr_nonnull +cx_attr_export +CxJsonValue *cx_json_obj_remove_cxstr(CxJsonValue *value, cxstring name); + #ifdef __cplusplus } // extern "C" @@ -1356,6 +1363,18 @@ return cx_json_obj_get_cxstr(value, cx_str(name)); } +static inline CxJsonValue *cxJsonObjRemove(CxJsonValue *value, cxstring name) { + return cx_json_obj_remove_cxstr(value, name); +} + +static inline CxJsonValue *cxJsonObjRemove(CxJsonValue *value, cxmutstr name) { + return cx_json_obj_remove_cxstr(value, cx_strcast(name)); +} + +static inline CxJsonValue *cxJsonObjRemove(CxJsonValue *value, const char *name) { + return cx_json_obj_remove_cxstr(value, cx_str(name)); +} + extern "C" { #else /** @@ -1397,6 +1416,43 @@ static inline CxJsonValue *cx_json_obj_get_str(const CxJsonValue *value, const char *name) { return cx_json_obj_get_cxstr(value, cx_str(name)); } + +/** + * Removes and returns a value corresponding to a key in a JSON object. + * + * If the @p value is not a JSON object, the behavior is undefined. + * + * This function, in contrast to cxJsonObjGet() returns @c NULL when the + * object does not contain @p name. + * + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key or @c NULL when the key is not part of the object + * @see cxJsonIsObject() + */ +#define cxJsonObjRemove(value, name) _Generic((name), \ + cxstring: cx_json_obj_remove_cxstr, \ + cxmutstr: cx_json_obj_remove_mutstr, \ + char*: cx_json_obj_remove_str, \ + const char*: cx_json_obj_remove_str) \ + (value, name) + +/** + * @copydoc cxJsonObjRemove() + */ +cx_attr_nonnull +static inline CxJsonValue *cx_json_obj_remove_mutstr(CxJsonValue *value, cxmutstr name) { + return cx_json_obj_remove_cxstr(value, cx_strcast(name)); +} + +/** + * @copydoc cxJsonObjRemove() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline CxJsonValue *cx_json_obj_remove_str(CxJsonValue *value, const char *name) { + return cx_json_obj_remove_cxstr(value, cx_str(name)); +} #endif #ifdef __cplusplus diff -r 6dfa1eb58ce3 -r cc153bffea28 src/json.c --- a/src/json.c Thu Aug 14 23:03:01 2025 +0200 +++ b/src/json.c Fri Aug 15 17:42:01 2025 +0200 @@ -46,22 +46,17 @@ return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name)); } -static CxJsonObjValue *json_find_objvalue(const CxJsonValue *obj, cxstring name) { +static size_t json_find_objvalue(const CxJsonValue *obj, cxstring name) { assert(obj->type == CX_JSON_OBJECT); CxJsonObjValue kv_dummy; kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length); - size_t index = cx_array_binary_search( + return cx_array_binary_search( obj->value.object.values, obj->value.object.values_size, sizeof(CxJsonObjValue), &kv_dummy, json_cmp_objvalue ); - if (index == obj->value.object.values_size) { - return NULL; - } else { - return &obj->value.object.values[index]; - } } static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) { @@ -1156,11 +1151,25 @@ } CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name) { - CxJsonObjValue *member = json_find_objvalue(value, name); - if (member == NULL) { + size_t index = json_find_objvalue(value, name); + if (index == value->value.object.values_size) { return &cx_json_value_nothing; } else { - return member->value; + return value->value.object.values[index].value; + } +} + +CxJsonValue *cx_json_obj_remove_cxstr(CxJsonValue *value, cxstring name) { + size_t index = json_find_objvalue(value, name); + if (index == value->value.object.values_size) { + return NULL; + } else { + CxJsonObjValue kv = value->value.object.values[index]; + cx_strfree_a(value->allocator, &kv.name); + // TODO: replace with cx_array_remove() + value->value.object.values_size--; + memmove(value->value.object.values + index, value->value.object.values + index + 1, (value->value.object.values_size - index) * sizeof(CxJsonObjValue)); + return kv.value; } } diff -r 6dfa1eb58ce3 -r cc153bffea28 tests/test_json.c --- a/tests/test_json.c Thu Aug 14 23:03:01 2025 +0200 +++ b/tests/test_json.c Fri Aug 15 17:42:01 2025 +0200 @@ -446,7 +446,7 @@ "\t\"data\":{\n" "\t\t\"obj\":{\n" "\t\t\t\"array\": [1, 2, 3, ?syntaxerror? ]\n" - "\t\t\"}\n" + "\t\t}\n" "\t},\n" "\t\"timestamp\":1729348561,\n" "}" @@ -482,6 +482,46 @@ } } +CX_TEST(test_json_object_remove_member) { + CxTestingAllocator talloc; + cx_testing_allocator_init(&talloc); + const CxAllocator *alloc = &talloc.base; + CX_TEST_DO { + CxJson json; + cxJsonInit(&json, alloc); + cxJsonFill(&json, cx_str( + "{\n" + "\t\"message\":\"success\",\n" + "\t\"data\":{\n" + "\t\t\"obj\":{\n" + "\t\t\t\"array\": [1, 2, 3]\n" + "\t\t}\n" + "\t},\n" + "\t\"timestamp\":1729348561\n" + "}" + )); + CxJsonValue *obj; + CX_TEST_ASSERT(CX_JSON_NO_ERROR == cxJsonNext(&json, &obj)); + cxJsonDestroy(&json); + + CX_TEST_ASSERT(cxJsonIsObject(cxJsonObjGet(obj, "data"))); + CxJsonValue *data = cxJsonObjRemove(obj, "data"); + CX_TEST_ASSERT(cxJsonIsObject(data)); + CX_TEST_ASSERT(!cxJsonIsObject(cxJsonObjGet(obj, "data"))); + CX_TEST_ASSERT(cxJsonIsObject(cxJsonObjGet(data, "obj"))); + + CX_TEST_ASSERT(NULL == cxJsonObjRemove(obj, "data")); + + // does not verify, yet, because we extracted an object + cxJsonValueFree(obj); + CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc)); + + cxJsonValueFree(data); + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + } + cx_testing_allocator_destroy(&talloc); +} + CX_TEST(test_json_large_nesting_depth) { CxJson json; CxJsonValue *d1; @@ -737,6 +777,9 @@ CX_TEST_ASSERT(cxJsonIsNumber(removed)); CX_TEST_ASSERT(cxJsonAsInteger(removed) == 6); CX_TEST_ASSERT(cxJsonArrSize(arr) == 5); + e = cxJsonArrGet(arr, 3); + CX_TEST_ASSERT(cxJsonIsNumber(e)); + CX_TEST_ASSERT(cxJsonAsInteger(e) == 12); e = cxJsonArrRemove(arr, 5); CX_TEST_ASSERT(e == NULL); cxJsonValueFree(arr); @@ -1227,6 +1270,7 @@ cx_test_register(suite, test_json_object_incomplete_token); 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_large_nesting_depth); cx_test_register(suite, test_json_number);