diff --git a/json_pointer.c b/json_pointer.c index 41ac620..0418baa 100644 --- a/json_pointer.c +++ b/json_pointer.c @@ -18,6 +18,7 @@ #include "json_object_private.h" #include "json_pointer.h" #include "json_pointer_private.h" +#include "linkhash.h" #include "strdup_compat.h" #include "vasprintf_compat.h" @@ -260,9 +261,17 @@ int json_pointer_get_internal(struct json_object *obj, const char *path, return -1; } rc = json_pointer_result_get_recursive(obj, path_copy, res); - /* re-map the path string to the const-path string */ + /* key_in_parent points at the decoded reference token in path_copy, + * which is freed below. The same offset in the caller's path is still + * escaped ("a~1b" rather than "a/b"), so use the key as it is stored in + * the parent object instead. + */ if (rc == 0 && json_object_is_type(res->parent, json_type_object) && res->key_in_parent) - res->key_in_parent = path + (res->key_in_parent - path_copy); + { + struct lh_entry *entry = + lh_table_lookup_entry(json_object_get_object(res->parent), res->key_in_parent); + res->key_in_parent = entry ? (const char *)lh_entry_k(entry) : NULL; + } free(path_copy); return rc; diff --git a/json_pointer_private.h b/json_pointer_private.h index e7ddf01..ab16794 100644 --- a/json_pointer_private.h +++ b/json_pointer_private.h @@ -20,7 +20,8 @@ struct json_pointer_get_result { struct json_object *parent; struct json_object *obj; // The key of the found object; only valid when parent is json_type_object - // Caution: re-uses tail end of the `path` argument to json_pointer_get_internal + // Caution: points at the key stored in `parent`, so it is only valid + // as long as that member exists const char *key_in_parent; // the index of the found object; only valid when parent is json_type_array uint32_t index_in_parent; diff --git a/tests/json_patch_tests.json b/tests/json_patch_tests.json index 9a0ecea..3da39d9 100644 --- a/tests/json_patch_tests.json +++ b/tests/json_patch_tests.json @@ -380,6 +380,17 @@ "patch": [{"op": "copy", "from": "/aa", "path": "/aab"}], "expected": {"aa": 1, "aab": 1} }, + { "comment": "Remove members whose names need escaping", + "doc": {"a/b": 1, "m~n": 2, "c": 3}, + "patch": [{"op": "remove", "path": "/a~1b"}, + {"op": "remove", "path": "/m~0n"}], + "expected": {"c": 3} }, + + { "comment": "Move from a member whose name needs escaping", + "doc": {"a/b": 1}, + "patch": [{"op": "move", "from": "/a~1b", "path": "/c"}], + "expected": {"c": 1} }, + { "doc": {"foo": 1, "baz": [{"qux": "hello"}]}, "patch": [{"op": "move", "from": "/foo", "path": "/bar"}], "expected": {"baz": [{"qux": "hello"}], "bar": 1} }, diff --git a/tests/test_json_patch.expected b/tests/test_json_patch.expected index bf9ffab..c553608 100644 --- a/tests/test_json_patch.expected +++ b/tests/test_json_patch.expected @@ -102,6 +102,8 @@ Testing '(null)', doc '{ "foo": [ "bar", "baz" ], "": 0, "a\/b": 1, "c%d": 2, "e Testing 'Move to same location has no effect', doc '{ "foo": 1 }' patch '[ { "op": "move", "from": "\/foo", "path": "\/foo" } ]' : OK Testing 'Move between sibling keys sharing a name prefix is not a parent/child move', doc '{ "aa": 1, "aab": 2 }' patch '[ { "op": "move", "from": "\/aa", "path": "\/aab" } ]' : OK Testing 'Copy between sibling keys sharing a name prefix is not a parent/child move', doc '{ "aa": 1, "aab": 2 }' patch '[ { "op": "copy", "from": "\/aa", "path": "\/aab" } ]' : OK +Testing 'Remove members whose names need escaping', doc '{ "a\/b": 1, "m~n": 2, "c": 3 }' patch '[ { "op": "remove", "path": "\/a~1b" }, { "op": "remove", "path": "\/m~0n" } ]' : OK +Testing 'Move from a member whose name needs escaping', doc '{ "a\/b": 1 }' patch '[ { "op": "move", "from": "\/a~1b", "path": "\/c" } ]' : OK Testing '(null)', doc '{ "foo": 1, "baz": [ { "qux": "hello" } ] }' patch '[ { "op": "move", "from": "\/foo", "path": "\/bar" } ]' : OK Testing '(null)', doc '{ "baz": [ { "qux": "hello" } ], "bar": 1 }' patch '[ { "op": "move", "from": "\/baz\/0\/qux", "path": "\/baz\/1" } ]' : OK Testing '(null)', doc '{ "baz": [ { "qux": "hello" } ], "bar": 1 }' patch '[ { "op": "copy", "from": "\/baz\/0", "path": "\/boo" } ]' : OK