From d9b375ff1b4ea0305566527e377ecf6545771e80 Mon Sep 17 00:00:00 2001 From: Wleeaf <179125857+wleeaf@users.noreply.github.com> Date: Sun, 25 Oct 2026 11:40:51 +0000 Subject: [PATCH] json_pointer: find null array elements json_pointer_get_single_path() returned ENOENT when the array element at an in-range index was NULL, which is how json-c stores a JSON null. A null object member was already found, but on [0, null, 2] "/1" failed with "not found", although RFC 6901 says it references the null value. The array index is bounds-checked just before, so a NULL element now resolves like a null member: json_pointer_get() returns 0 and sets *res to NULL. This also fixes json_patch_apply(), which failed every "test", "replace" and "remove" on a null array element, and every "move" and "copy" from one, with "Did not find element referenced by path field". Added a json_pointer_get() test, and json_patch_tests.json cases that mirror the existing null object member ones. --- json_pointer.c | 14 ++++---------- tests/json_patch_tests.json | 25 +++++++++++++++++++++++++ tests/test_json_patch.expected | 5 +++++ tests/test_json_pointer.c | 12 ++++++++++++ tests/test_json_pointer.expected | 1 + 5 files changed, 47 insertions(+), 10 deletions(-) diff --git a/json_pointer.c b/json_pointer.c index 0418baa..e05bc46 100644 --- a/json_pointer.c +++ b/json_pointer.c @@ -102,16 +102,10 @@ static int json_pointer_get_single_path(struct json_object *obj, char *path, return -1; } - obj = json_object_array_get_idx(obj, *idx); - if (obj) - { - if (value) - *value = obj; - return 0; - } - /* Entry not found */ - errno = ENOENT; - return -1; + /* A null element is stored as NULL, but it exists */ + if (value) + *value = json_object_array_get_idx(obj, *idx); + return 0; } /* RFC states that we first must eval all ~1 then all ~0 */ diff --git a/tests/json_patch_tests.json b/tests/json_patch_tests.json index 3da39d9..eb964a8 100644 --- a/tests/json_patch_tests.json +++ b/tests/json_patch_tests.json @@ -291,6 +291,31 @@ "expected": {"foo": null}, "comment": "null value should still be valid obj property replace other value" }, + { "doc": {"foo": [null]}, + "patch": [{"op": "test", "path": "/foo/0", "value": null}], + "expected": {"foo": [null]}, + "comment": "null value should be valid array element" }, + + { "doc": {"foo": [null]}, + "patch": [{"op": "replace", "path": "/foo/0", "value": "truthy"}], + "expected": {"foo": ["truthy"]}, + "comment": "null value should be valid array element to be replaced with something truthy" }, + + { "doc": {"foo": [null, 1]}, + "patch": [{"op": "move", "from": "/foo/0", "path": "/bar"}], + "expected": {"foo": [1], "bar": null}, + "comment": "null value should be valid array element to be moved" }, + + { "doc": {"foo": [null]}, + "patch": [{"op": "copy", "from": "/foo/0", "path": "/foo/-"}], + "expected": {"foo": [null, null]}, + "comment": "null value should be valid array element to be copied" }, + + { "doc": {"foo": [null, 1]}, + "patch": [{"op": "remove", "path": "/foo/0"}], + "expected": {"foo": [1]}, + "comment": "null value should be valid array element to be removed" }, + { "doc": {"foo": {"foo": 1, "bar": 2}}, "patch": [{"op": "test", "path": "/foo", "value": {"bar": 2, "foo": 1}}], "expected": {"foo": {"foo": 1, "bar": 2}}, diff --git a/tests/test_json_patch.expected b/tests/test_json_patch.expected index c553608..041c014 100644 --- a/tests/test_json_patch.expected +++ b/tests/test_json_patch.expected @@ -89,6 +89,11 @@ Testing 'null value should be valid obj property to be moved', doc '{ "foo": nul Testing 'null value should be valid obj property to be copied', doc '{ "foo": null }' patch '[ { "op": "copy", "from": "\/foo", "path": "\/bar" } ]' : OK Testing 'null value should be valid obj property to be removed', doc '{ "foo": null }' patch '[ { "op": "remove", "path": "\/foo" } ]' : OK Testing 'null value should still be valid obj property replace other value', doc '{ "foo": "bar" }' patch '[ { "op": "replace", "path": "\/foo", "value": null } ]' : OK +Testing 'null value should be valid array element', doc '{ "foo": [ null ] }' patch '[ { "op": "test", "path": "\/foo\/0", "value": null } ]' : OK +Testing 'null value should be valid array element to be replaced with something truthy', doc '{ "foo": [ null ] }' patch '[ { "op": "replace", "path": "\/foo\/0", "value": "truthy" } ]' : OK +Testing 'null value should be valid array element to be moved', doc '{ "foo": [ null, 1 ] }' patch '[ { "op": "move", "from": "\/foo\/0", "path": "\/bar" } ]' : OK +Testing 'null value should be valid array element to be copied', doc '{ "foo": [ null ] }' patch '[ { "op": "copy", "from": "\/foo\/0", "path": "\/foo\/-" } ]' : OK +Testing 'null value should be valid array element to be removed', doc '{ "foo": [ null, 1 ] }' patch '[ { "op": "remove", "path": "\/foo\/0" } ]' : OK Testing 'test should pass despite rearrangement', doc '{ "foo": { "foo": 1, "bar": 2 } }' patch '[ { "op": "test", "path": "\/foo", "value": { "bar": 2, "foo": 1 } } ]' : OK Testing 'test should pass despite (nested) rearrangement', doc '{ "foo": [ { "foo": 1, "bar": 2 } ] }' patch '[ { "op": "test", "path": "\/foo", "value": [ { "bar": 2, "foo": 1 } ] } ]' : OK Testing 'test should pass - no error', doc '{ "foo": { "bar": [ 1, 2, 5, 4 ] } }' patch '[ { "op": "test", "path": "\/foo", "value": { "bar": [ 1, 2, 5, 4 ] } } ]' : OK diff --git a/tests/test_json_pointer.c b/tests/test_json_pointer.c index fb52a0d..e8c9a8b 100644 --- a/tests/test_json_pointer.c +++ b/tests/test_json_pointer.c @@ -122,6 +122,18 @@ static void test_example_get(void) test_example_int(jo1, json_pointers[i].s, json_pointers[i].i); json_object_put(jo1); + + /* Test /1 == null, a null array element is found and returned as NULL */ + jo1 = json_tokener_parse("[0, null, 2]"); + jo2 = jo1; + assert(0 == json_pointer_get(jo1, "/1", NULL)); + assert(0 == json_pointer_get(jo1, "/1", &jo2)); + assert(NULL == jo2); + errno = 0; + assert(0 != json_pointer_get(jo1, "/1/foo", NULL)); + assert(errno == ENOENT); + printf("PASSED - GET - /1 == null\n"); + json_object_put(jo1); } /* I'm not too happy with the RFC example to test the recursion of the json_pointer_get() function */ diff --git a/tests/test_json_pointer.expected b/tests/test_json_pointer.expected index 18d5ea3..062a4a4 100644 --- a/tests/test_json_pointer.expected +++ b/tests/test_json_pointer.expected @@ -12,6 +12,7 @@ PASSED - GET - /i\j == 5 PASSED - GET - /k"l == 6 PASSED - GET - / == 7 PASSED - GET - /m~0n == 8 +PASSED - GET - /1 == null { "arr": [ { "obj": [ { }, { }, { "obj1": 0, "obj2": "1" } ] } ], "obj": { "obj": { "obj": [ { "obj1": 0, "obj2": "1" } ] } } } PASSED - GET - RECURSION TEST PASSED - GET - LOADED TEST JSON