add cxJsonObjRemove() #627

Fri, 15 Aug 2025 17:42:01 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 15 Aug 2025 17:42:01 +0200
changeset 1338
cc153bffea28
parent 1337
6dfa1eb58ce3
child 1339
bff0a078523d

add cxJsonObjRemove() #627

CHANGELOG file | annotate | diff | comparison | revisions
src/cx/json.h file | annotate | diff | comparison | revisions
src/json.c file | annotate | diff | comparison | revisions
tests/test_json.c file | annotate | diff | comparison | revisions
--- 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
--- 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
--- 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;
     }
 }
 
--- 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);

mercurial