From cb4727c4a931a471f7d2f1594b162a71349b4dfe Mon Sep 17 00:00:00 2001 From: Henrique Cabral Date: Fri, 11 Jan 2019 12:50:37 +0000 Subject: [PATCH 1/3] Add json_object_update_recursive() Support merging values nested within objects. For instance, merging: { "foo": 1, "bar": { "baz": 2 } } with { "bar": { "baz": 3 } } results in { "foo": 1, "bar": { "baz": 3 } } instead of overwriting the value for the bar key. --- doc/apiref.rst | 6 ++++ src/jansson.def | 1 + src/jansson.h | 1 + src/value.c | 20 ++++++++++++++ test/suites/api/test_object.c | 52 +++++++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+) diff --git a/doc/apiref.rst b/doc/apiref.rst index 3c4fa47..07962d1 100644 --- a/doc/apiref.rst +++ b/doc/apiref.rst @@ -708,6 +708,12 @@ allowed in object keys. .. versionadded:: 2.3 +.. function:: int json_object_update_recursive(json_t *object, json_t *other) + + Like :func:`json_object_update()`, but object values in *other* are + recursively merged with the corresponding values in *object* if they are also + objects, instead of overwriting them. Returns 0 on success or -1 on error. + .. function:: json_object_foreach(object, key, value) Iterate over every key-value pair of ``object``, running the block diff --git a/src/jansson.def b/src/jansson.def index ed72829..55b39c8 100644 --- a/src/jansson.def +++ b/src/jansson.def @@ -41,6 +41,7 @@ EXPORTS json_object_update json_object_update_existing json_object_update_missing + json_object_update_recursive json_object_iter json_object_iter_at json_object_iter_next diff --git a/src/jansson.h b/src/jansson.h index b2b980b..337ddac 100644 --- a/src/jansson.h +++ b/src/jansson.h @@ -199,6 +199,7 @@ int json_object_clear(json_t *object); int json_object_update(json_t *object, json_t *other); int json_object_update_existing(json_t *object, json_t *other); int json_object_update_missing(json_t *object, json_t *other); +int json_object_update_recursive(json_t *object, json_t *other); void *json_object_iter(json_t *object); void *json_object_iter_at(json_t *object, const char *key); void *json_object_key_to_iter(const char *key); diff --git a/src/value.c b/src/value.c index 7a000f8..1996232 100644 --- a/src/value.c +++ b/src/value.c @@ -214,6 +214,26 @@ int json_object_update_missing(json_t *object, json_t *other) return 0; } +int json_object_update_recursive(json_t *object, json_t *other) +{ + const char *key; + json_t *value; + + if(!json_is_object(object) || !json_is_object(other)) + return -1; + + json_object_foreach(other, key, value) { + json_t *v = json_object_get(object, key); + + if(json_is_object(v) && json_is_object(value)) + json_object_update_recursive(v, value); + else + json_object_set_nocheck(object, key, value); + } + + return 0; +} + void *json_object_iter(json_t *json) { json_object_t *object; diff --git a/test/suites/api/test_object.c b/test/suites/api/test_object.c index 521ca81..ac10936 100644 --- a/test/suites/api/test_object.c +++ b/test/suites/api/test_object.c @@ -207,6 +207,57 @@ static void test_conditional_updates() json_decref(other); } +static void test_recursive_updates() +{ + json_t *invalid, *object, *other; + + invalid = json_integer(42); + + object = json_pack("{sis{si}}", "foo", 1, "bar", "baz", 2); + other = json_pack("{sisisi}", "foo", 3, "bar", 4, "baz", 5); + + if(!json_object_update_recursive(invalid, other)) + fail("json_object_update_recursive accepted non-object argument"); + + json_decref(invalid); + + if(json_object_update_recursive(object, other)) + fail("json_object_update_recursive failed"); + + if(json_object_size(object) != 3) + fail("invalid size after update"); + + if(json_integer_value(json_object_get(object, "foo")) != 3) + fail("json_object_update_recursive failed to update existing key"); + + if(json_integer_value(json_object_get(object, "bar")) != 4) + fail("json_object_update_recursive failed to overwrite object"); + + if(json_integer_value(json_object_get(object, "baz")) != 5) + fail("json_object_update_recursive didn't add new item"); + + json_decref(object); + json_decref(other); + + object = json_pack("{sis{si}}", "foo", 1, "bar", "baz", 2); + other = json_pack("{s{si}}", "bar", "baz", 3); + + if(json_object_update_recursive(object, other)) + fail("json_object_update_recursive failed"); + + if(json_object_size(object) != 2) + fail("invalid size after update"); + + if(!json_object_get(object, "foo")) + fail("json_object_update_recursive removed existing key"); + + if(json_integer_value(json_object_get(json_object_get(object, "bar"), "baz")) != 3) + fail("json_object_update_recursive failed to update nested value"); + + json_decref(object); + json_decref(other); +} + static void test_circular() { json_t *object1, *object2; @@ -667,6 +718,7 @@ static void run_tests() test_update(); test_set_many_keys(); test_conditional_updates(); + test_recursive_updates(); test_circular(); test_set_nocheck(); test_iterators(); From 00d2d274bc4260d9197d5003cfb4531f7eca494b Mon Sep 17 00:00:00 2001 From: allen Date: Sat, 12 Oct 2019 15:36:05 +0800 Subject: [PATCH 2/3] add loop check for json_object_update_recursive function --- src/value.c | 50 ++++++++++++++++++++++++++++++----- test/suites/api/test_object.c | 33 ++++++++++++++++++++++- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/value.c b/src/value.c index 1996232..e09a485 100644 --- a/src/value.c +++ b/src/value.c @@ -37,7 +37,7 @@ static JSON_INLINE int isnan(double x) { return x != x; } static JSON_INLINE int isinf(double x) { return !isnan(x) && isnan(x - x); } #endif -json_t *do_deep_copy(const json_t *, hashtable_t *); +json_t *do_deep_copy(const json_t *json, hashtable_t *parents); static JSON_INLINE void json_init(json_t *json, json_type type) { @@ -214,24 +214,56 @@ int json_object_update_missing(json_t *object, json_t *other) return 0; } -int json_object_update_recursive(json_t *object, json_t *other) +int do_object_update_recursive(json_t *object, json_t *other, hashtable_t *parents) { const char *key; json_t *value; + char loop_key[LOOP_KEY_LEN]; + int res = 0; if(!json_is_object(object) || !json_is_object(other)) return -1; + if(jsonp_loop_check(parents, other, loop_key, sizeof(loop_key))) + return -1; + json_object_foreach(other, key, value) { json_t *v = json_object_get(object, key); if(json_is_object(v) && json_is_object(value)) - json_object_update_recursive(v, value); + { + if(do_object_update_recursive(v, value, parents)) + { + res = -1; + break; + } + } else - json_object_set_nocheck(object, key, value); + { + if(json_object_set_nocheck(object, key, value)) + { + res = -1; + break; + } + } } - return 0; + hashtable_del(parents, loop_key); + + return res; +} + +int json_object_update_recursive(json_t *object, json_t *other) +{ + int res; + hashtable_t parents_set; + + if (hashtable_init(&parents_set)) + return -1; + res = do_object_update_recursive(object, other, &parents_set); + hashtable_close(&parents_set); + + return res; } void *json_object_iter(json_t *json) @@ -349,7 +381,7 @@ static json_t *json_object_deep_copy(const json_t *object, hashtable_t *parents) result = json_object(); if(!result) - return NULL; + goto out; /* Cannot use json_object_foreach because object has to be cast non-const */ @@ -368,6 +400,8 @@ static json_t *json_object_deep_copy(const json_t *object, hashtable_t *parents) } iter = json_object_iter_next((json_t *)object, iter); } + +out: hashtable_del(parents, loop_key); return result; @@ -668,7 +702,7 @@ static json_t *json_array_deep_copy(const json_t *array, hashtable_t *parents) result = json_array(); if(!result) - return NULL; + goto out; for(i = 0; i < json_array_size(array); i++) { @@ -679,6 +713,8 @@ static json_t *json_array_deep_copy(const json_t *array, hashtable_t *parents) break; } } + +out: hashtable_del(parents, loop_key); return result; diff --git a/test/suites/api/test_object.c b/test/suites/api/test_object.c index ac10936..0493e98 100644 --- a/test/suites/api/test_object.c +++ b/test/suites/api/test_object.c @@ -209,7 +209,7 @@ static void test_conditional_updates() static void test_recursive_updates() { - json_t *invalid, *object, *other; + json_t *invalid, *object, *other, *barBefore, *barAfter; invalid = json_integer(42); @@ -241,6 +241,10 @@ static void test_recursive_updates() object = json_pack("{sis{si}}", "foo", 1, "bar", "baz", 2); other = json_pack("{s{si}}", "bar", "baz", 3); + barBefore = json_object_get(object, "bar"); + + if(!barBefore) + fail("can't get bar object before json_object_update_recursive"); if(json_object_update_recursive(object, other)) fail("json_object_update_recursive failed"); @@ -254,6 +258,33 @@ static void test_recursive_updates() if(json_integer_value(json_object_get(json_object_get(object, "bar"), "baz")) != 3) fail("json_object_update_recursive failed to update nested value"); + barAfter = json_object_get(object, "bar"); + if(!barAfter) + fail("can't get bar object after json_object_update_recursive"); + + if(barBefore != barAfter) + fail("bar object reference changed after json_object_update_recursive"); + + json_decref(object); + json_decref(other); + + /* check circular reference */ + object = json_pack("{s{s{si}}}", "foo", "bar", "baz", 2); + other = json_pack("{s{s{si}}}", "foo", "bar", "baz", 2); + json_object_set(json_object_get(json_object_get(object, "foo"), "bar"), "baz", + json_object_get(other, "foo")); + json_object_set(json_object_get(json_object_get(other, "foo"), "bar"), "baz", + json_object_get(other, "foo")); + + if(!json_object_update_recursive(object, other)) + fail("json_object_update_recursive update a circular reference!"); + + json_object_set_new(json_object_get(json_object_get(other, "foo"), "bar"), "baz", + json_integer(1)); + + if(json_object_update_recursive(object, other)) + fail("json_object_update_recursive failed!"); + json_decref(object); json_decref(other); } From fb602f331bb356aca4b2ec63c7db4da34773a381 Mon Sep 17 00:00:00 2001 From: allen Date: Mon, 14 Oct 2019 17:32:24 +0800 Subject: [PATCH 3/3] update the test case of json_object_update_recursive --- test/suites/api/test_object.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/suites/api/test_object.c b/test/suites/api/test_object.c index 0493e98..a646ae9 100644 --- a/test/suites/api/test_object.c +++ b/test/suites/api/test_object.c @@ -269,10 +269,8 @@ static void test_recursive_updates() json_decref(other); /* check circular reference */ - object = json_pack("{s{s{si}}}", "foo", "bar", "baz", 2); + object = json_pack("{s{s{s{si}}}}", "foo", "bar", "baz", "xxx", 2); other = json_pack("{s{s{si}}}", "foo", "bar", "baz", 2); - json_object_set(json_object_get(json_object_get(object, "foo"), "bar"), "baz", - json_object_get(other, "foo")); json_object_set(json_object_get(json_object_get(other, "foo"), "bar"), "baz", json_object_get(other, "foo"));