Report one-character non-numeric array indices like longer ones

A JSON pointer reference token that is not a number but has only one
character (e.g. "/a/x") was reported as out_of_range.404 ("unresolved
reference token"), because the "is not a number" check only ran for
tokens longer than one character; "/a/xy" got parse_error.109. Both now
throw parse_error.109. "-" and the empty token are still reported as
out_of_range.404. As a consequence, value(json_pointer, default) on an
array now throws for "/x" as it already did for "/xy".

Also document why ordered_map::erase's destroy/placement-new loop on
pair<const Key, T> is kept despite [basic.life]/8 before C++20.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-10 12:42:06 +02:00
1 parent 07ec1a4e31
commit 26b2494aa7
7 files changed
+67 -8

No files matched your search

+2
View File
@@ -535,6 +535,8 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j
// Test malformed index (non-numeric) throws parse_error
CHECK_THROWS_WITH_AS(j_array.value("/foo"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'foo' is not a number", typename Json::parse_error&);
CHECK_THROWS_WITH_AS(j_array_const.value("/foo"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'foo' is not a number", typename Json::parse_error&);
CHECK_THROWS_WITH_AS(j_array.value("/x"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'x' is not a number", typename Json::parse_error&);
CHECK_THROWS_WITH_AS(j_array_const.value("/x"_json_pointer, 1), "[json.exception.parse_error.109] parse error: array index 'x' is not a number", typename Json::parse_error&);
// Test leading-zero index throws parse_error
CHECK_THROWS_WITH_AS(j_array.value("/01"_json_pointer, 1), "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", typename Json::parse_error&);
+15
View File
@@ -1713,6 +1713,21 @@ TEST_CASE("JSON patch - move where 'from' is a proper prefix of 'path' (regressi
CHECK(doc.patch(patch) == R"({"b": 1})"_json);
}
SECTION("array index tokens that are not a number")
{
json const doc = R"({"a": [1, 2]})"_json;
// "-" cannot be removed and is reported as unresolved
json const patch_dash = {{{"op", "remove"}, {"path", "/a/-"}}};
CHECK_THROWS_WITH_AS(doc.patch(patch_dash), "[json.exception.out_of_range.404] unresolved reference token '-'", json::out_of_range&);
// a single character is reported like a longer token
json const patch_x = {{{"op", "remove"}, {"path", "/a/x"}}};
CHECK_THROWS_WITH_AS(doc.patch(patch_x), "[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&);
json const patch_xy = {{{"op", "remove"}, {"path", "/a/xy"}}};
CHECK_THROWS_WITH_AS(doc.patch(patch_xy), "[json.exception.parse_error.109] parse error: array index 'xy' is not a number", json::parse_error&);
}
SECTION("the array-append token '-' is an ordinary child token")
{
// "-" (append-to-array) addresses a location *inside* the array,
+21
View File
@@ -421,6 +421,27 @@ TEST_CASE("JSON pointers")
CHECK_THROWS_WITH_AS(json({{"/list/0", 1}, {"/list/1", 2}, {"/list/three", 3}}).unflatten(),
"[json.exception.parse_error.109] parse error: array index 'three' is not a number", json::parse_error&);
// a single-character token that is not a digit is reported like a
// longer one (parse_error.109), not as unresolved (out_of_range.404)
CHECK_THROWS_WITH_AS(j["/x"_json_pointer] = 1,
"[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&);
CHECK_THROWS_WITH_AS(j_const["/x"_json_pointer] == 1,
"[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&);
CHECK_THROWS_WITH_AS(j.at("/x"_json_pointer) = 1,
"[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&);
CHECK_THROWS_WITH_AS(j_const.at("/x"_json_pointer) == 1,
"[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&);
CHECK_THROWS_WITH_AS(j.at("/+"_json_pointer),
"[json.exception.parse_error.109] parse error: array index '+' is not a number", json::parse_error&);
CHECK(!j.contains("/x"_json_pointer));
CHECK(!j_const.contains("/x"_json_pointer));
CHECK_THROWS_WITH_AS(json({{"/list/0", 1}, {"/list/x", 2}}).unflatten(),
"[json.exception.parse_error.109] parse error: array index 'x' is not a number", json::parse_error&);
// "-" is a valid reference token, so it is still reported as unresolved
CHECK_THROWS_WITH_AS(json({{"/list/0", 1}, {"/list/-", 2}}).unflatten(),
"[json.exception.out_of_range.404] unresolved reference token '-'", json::out_of_range&);
// assign to "-"
j["/-"_json_pointer] = 99;
CHECK(j == json({1, 13, 3, 33, nullptr, 55, 99}));