mirror of
https://github.com/nlohmann/json.git
synced 2026-07-03 17:24:18 +00:00
Extend value to arrays when using JSON pointers (#5223)
* ✨ extend value to arrays when using JSON pointers Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 💚 avoid exceptions Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 💚 avoid exceptions Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -94,8 +94,12 @@ changes to any JSON value.
|
||||
3. The function can throw the following exceptions:
|
||||
- Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `default_value` does not match
|
||||
the type of the value at `ptr`
|
||||
- Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if the JSON value is not an object;
|
||||
in that case, using `value()` with a JSON pointer makes no sense.
|
||||
- Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if the JSON value is not an array
|
||||
or object; in that case, using `value()` with a JSON pointer makes no sense.
|
||||
- Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in the passed
|
||||
JSON pointer `ptr` begins with '0'.
|
||||
- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in the passed
|
||||
JSON pointer `ptr` is not a number.
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -180,4 +184,4 @@ changes to any JSON value.
|
||||
|
||||
1. Added in version 1.0.0. Changed parameter `default_value` type from `const ValueType&` to `ValueType&&` in version 3.11.0.
|
||||
2. Added in version 3.11.0. Made `ValueType` the first template parameter in version 3.11.2.
|
||||
3. Added in version 2.0.2.
|
||||
3. Added in version 2.0.2. Extended to work with arrays in version 3.12.x.
|
||||
|
||||
@@ -33,7 +33,8 @@ you want to access and a default value in case there is no value stored with tha
|
||||
|
||||
!!! failure "Exceptions"
|
||||
|
||||
- `value` can only be used with objects. For other types, a [`basic_json::type_error`](../../home/exceptions.md#jsonexceptiontype_error306) is thrown.
|
||||
- With string keys, `value` can only be used with objects. For other types, a [`basic_json::type_error`](../../home/exceptions.md#jsonexceptiontype_error306) is thrown.
|
||||
- With JSON Pointers, `value` can be used with both objects and arrays. For other types (null, boolean, number, string), a [`basic_json::type_error`](../../home/exceptions.md#jsonexceptiontype_error306) is thrown.
|
||||
|
||||
!!! warning "Return type"
|
||||
|
||||
|
||||
@@ -480,8 +480,14 @@ class json_pointer
|
||||
") is out of range"), ptr));
|
||||
}
|
||||
|
||||
// note: at performs range check
|
||||
ptr = &ptr->at(array_index<BasicJsonType>(reference_token));
|
||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
JSON_THROW(detail::out_of_range::create(401, detail::concat(
|
||||
"array index ", std::to_string(idx), " is out of range"), ptr));
|
||||
}
|
||||
ptr = &ptr->operator[](idx);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -587,8 +593,14 @@ class json_pointer
|
||||
") is out of range"), ptr));
|
||||
}
|
||||
|
||||
// note: at performs range check
|
||||
ptr = &ptr->at(array_index<BasicJsonType>(reference_token));
|
||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
JSON_THROW(detail::out_of_range::create(401, detail::concat(
|
||||
"array index ", std::to_string(idx), " is out of range"), ptr));
|
||||
}
|
||||
ptr = &ptr->operator[](idx);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -608,6 +620,82 @@ class json_pointer
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief return a pointer to the pointed to value, or `nullptr` if the
|
||||
pointer cannot be resolved because a key is missing, an array
|
||||
index is out of range, or the array index is "-"
|
||||
|
||||
@note unlike get_checked(), this never throws for those cases, so it
|
||||
can be used to implement a non-throwing fallback (e.g. value())
|
||||
that also works when exceptions are disabled
|
||||
|
||||
@throw parse_error.106 if an array index begins with '0'
|
||||
@throw parse_error.109 if an array index was not a number
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
const BasicJsonType* get_checked_or_null(const BasicJsonType* ptr) const
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (ptr->type())
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
const auto it = ptr->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->end()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &*it;
|
||||
break;
|
||||
}
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// may throw parse_error.106/109 for a malformed index; an
|
||||
// index that is syntactically valid but cannot be
|
||||
// represented (out_of_range.404/410) is treated like an
|
||||
// out-of-range index below
|
||||
typename BasicJsonType::size_type idx{};
|
||||
JSON_TRY
|
||||
{
|
||||
idx = array_index<BasicJsonType>(reference_token);
|
||||
}
|
||||
JSON_INTERNAL_CATCH (detail::out_of_range&)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &ptr->operator[](idx);
|
||||
break;
|
||||
}
|
||||
|
||||
case detail::value_t::null:
|
||||
case detail::value_t::string:
|
||||
case detail::value_t::boolean:
|
||||
case detail::value_t::number_integer:
|
||||
case detail::value_t::number_unsigned:
|
||||
case detail::value_t::number_float:
|
||||
case detail::value_t::binary:
|
||||
case detail::value_t::discarded:
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/*!
|
||||
@throw parse_error.106 if an array index begins with '0'
|
||||
@throw parse_error.109 if an array index was not a number
|
||||
|
||||
+14
-16
@@ -2393,19 +2393,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
&& !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >
|
||||
ValueType value(const json_pointer& ptr, const ValueType& default_value) const
|
||||
{
|
||||
// value only works for objects
|
||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
||||
// value only works for arrays and objects
|
||||
if (JSON_HEDLEY_LIKELY(is_structured()))
|
||||
{
|
||||
// If the pointer resolves to a value, return it. Otherwise, return
|
||||
// 'default_value'.
|
||||
JSON_TRY
|
||||
const auto* res = ptr.get_checked_or_null(this);
|
||||
if (JSON_HEDLEY_LIKELY(res != nullptr))
|
||||
{
|
||||
return ptr.get_checked(this).template get<ValueType>();
|
||||
}
|
||||
JSON_INTERNAL_CATCH (out_of_range&)
|
||||
{
|
||||
return default_value;
|
||||
return res->template get<ValueType>();
|
||||
}
|
||||
|
||||
return default_value;
|
||||
}
|
||||
|
||||
JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this));
|
||||
@@ -2419,19 +2418,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
&& !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >
|
||||
ReturnType value(const json_pointer& ptr, ValueType && default_value) const
|
||||
{
|
||||
// value only works for objects
|
||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
||||
// value only works for arrays and objects
|
||||
if (JSON_HEDLEY_LIKELY(is_structured()))
|
||||
{
|
||||
// If the pointer resolves to a value, return it. Otherwise, return
|
||||
// 'default_value'.
|
||||
JSON_TRY
|
||||
const auto* res = ptr.get_checked_or_null(this);
|
||||
if (JSON_HEDLEY_LIKELY(res != nullptr))
|
||||
{
|
||||
return ptr.get_checked(this).template get<ReturnType>();
|
||||
}
|
||||
JSON_INTERNAL_CATCH (out_of_range&)
|
||||
{
|
||||
return std::forward<ValueType>(default_value);
|
||||
return res->template get<ReturnType>();
|
||||
}
|
||||
|
||||
return std::forward<ValueType>(default_value);
|
||||
}
|
||||
|
||||
JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this));
|
||||
|
||||
@@ -15401,8 +15401,14 @@ class json_pointer
|
||||
") is out of range"), ptr));
|
||||
}
|
||||
|
||||
// note: at performs range check
|
||||
ptr = &ptr->at(array_index<BasicJsonType>(reference_token));
|
||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
JSON_THROW(detail::out_of_range::create(401, detail::concat(
|
||||
"array index ", std::to_string(idx), " is out of range"), ptr));
|
||||
}
|
||||
ptr = &ptr->operator[](idx);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -15508,8 +15514,14 @@ class json_pointer
|
||||
") is out of range"), ptr));
|
||||
}
|
||||
|
||||
// note: at performs range check
|
||||
ptr = &ptr->at(array_index<BasicJsonType>(reference_token));
|
||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
JSON_THROW(detail::out_of_range::create(401, detail::concat(
|
||||
"array index ", std::to_string(idx), " is out of range"), ptr));
|
||||
}
|
||||
ptr = &ptr->operator[](idx);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -15529,6 +15541,82 @@ class json_pointer
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief return a pointer to the pointed to value, or `nullptr` if the
|
||||
pointer cannot be resolved because a key is missing, an array
|
||||
index is out of range, or the array index is "-"
|
||||
|
||||
@note unlike get_checked(), this never throws for those cases, so it
|
||||
can be used to implement a non-throwing fallback (e.g. value())
|
||||
that also works when exceptions are disabled
|
||||
|
||||
@throw parse_error.106 if an array index begins with '0'
|
||||
@throw parse_error.109 if an array index was not a number
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
const BasicJsonType* get_checked_or_null(const BasicJsonType* ptr) const
|
||||
{
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
switch (ptr->type())
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
const auto it = ptr->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->end()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &*it;
|
||||
break;
|
||||
}
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// may throw parse_error.106/109 for a malformed index; an
|
||||
// index that is syntactically valid but cannot be
|
||||
// represented (out_of_range.404/410) is treated like an
|
||||
// out-of-range index below
|
||||
typename BasicJsonType::size_type idx{};
|
||||
JSON_TRY
|
||||
{
|
||||
idx = array_index<BasicJsonType>(reference_token);
|
||||
}
|
||||
JSON_INTERNAL_CATCH (detail::out_of_range&)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &ptr->operator[](idx);
|
||||
break;
|
||||
}
|
||||
|
||||
case detail::value_t::null:
|
||||
case detail::value_t::string:
|
||||
case detail::value_t::boolean:
|
||||
case detail::value_t::number_integer:
|
||||
case detail::value_t::number_unsigned:
|
||||
case detail::value_t::number_float:
|
||||
case detail::value_t::binary:
|
||||
case detail::value_t::discarded:
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/*!
|
||||
@throw parse_error.106 if an array index begins with '0'
|
||||
@throw parse_error.109 if an array index was not a number
|
||||
@@ -22911,19 +22999,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
&& !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >
|
||||
ValueType value(const json_pointer& ptr, const ValueType& default_value) const
|
||||
{
|
||||
// value only works for objects
|
||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
||||
// value only works for arrays and objects
|
||||
if (JSON_HEDLEY_LIKELY(is_structured()))
|
||||
{
|
||||
// If the pointer resolves to a value, return it. Otherwise, return
|
||||
// 'default_value'.
|
||||
JSON_TRY
|
||||
const auto* res = ptr.get_checked_or_null(this);
|
||||
if (JSON_HEDLEY_LIKELY(res != nullptr))
|
||||
{
|
||||
return ptr.get_checked(this).template get<ValueType>();
|
||||
}
|
||||
JSON_INTERNAL_CATCH (out_of_range&)
|
||||
{
|
||||
return default_value;
|
||||
return res->template get<ValueType>();
|
||||
}
|
||||
|
||||
return default_value;
|
||||
}
|
||||
|
||||
JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this));
|
||||
@@ -22937,19 +23024,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
&& !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >
|
||||
ReturnType value(const json_pointer& ptr, ValueType && default_value) const
|
||||
{
|
||||
// value only works for objects
|
||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
||||
// value only works for arrays and objects
|
||||
if (JSON_HEDLEY_LIKELY(is_structured()))
|
||||
{
|
||||
// If the pointer resolves to a value, return it. Otherwise, return
|
||||
// 'default_value'.
|
||||
JSON_TRY
|
||||
const auto* res = ptr.get_checked_or_null(this);
|
||||
if (JSON_HEDLEY_LIKELY(res != nullptr))
|
||||
{
|
||||
return ptr.get_checked(this).template get<ReturnType>();
|
||||
}
|
||||
JSON_INTERNAL_CATCH (out_of_range&)
|
||||
{
|
||||
return std::forward<ValueType>(default_value);
|
||||
return res->template get<ReturnType>();
|
||||
}
|
||||
|
||||
return std::forward<ValueType>(default_value);
|
||||
}
|
||||
|
||||
JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this));
|
||||
|
||||
@@ -55,5 +55,5 @@ int main()
|
||||
// use every result so the references cannot be optimized away
|
||||
return (a == 1 && last == 3 && b == 2 && lit.size() == 3
|
||||
&& m.size() == 1 && !dumped.empty() && !os.str().empty())
|
||||
? 0 : 1;
|
||||
? 0 : 1;
|
||||
}
|
||||
|
||||
@@ -419,14 +419,6 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j
|
||||
CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with string", typename Json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("array")
|
||||
{
|
||||
Json j_nonobject(Json::value_t::array);
|
||||
const Json j_nonobject_const(Json::value_t::array);
|
||||
CHECK_THROWS_WITH_AS(j_nonobject.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with array", typename Json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("number (integer)")
|
||||
{
|
||||
Json j_nonobject(Json::value_t::number_integer);
|
||||
@@ -451,6 +443,55 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j
|
||||
CHECK_THROWS_WITH_AS(j_nonobject_const.value("/foo"_json_pointer, 1), "[json.exception.type_error.306] cannot use value() with number", typename Json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("access on array type")
|
||||
{
|
||||
Json j_array = Json::array({j});
|
||||
const Json j_array_const = Json::array({j});
|
||||
|
||||
CHECK(j_array.value("/0/integer"_json_pointer, 2) == 1);
|
||||
CHECK(j_array.value("/0/integer"_json_pointer, 1.0) == Approx(1));
|
||||
CHECK(j_array.value("/0/unsigned"_json_pointer, 2) == 1u);
|
||||
CHECK(j_array.value("/0/unsigned"_json_pointer, 1.0) == Approx(1u));
|
||||
CHECK(j_array.value("/0/null"_json_pointer, Json(1)) == Json());
|
||||
CHECK(j_array.value("/0/boolean"_json_pointer, false) == true);
|
||||
CHECK(j_array.value("/0/string"_json_pointer, "bar") == "hello world");
|
||||
CHECK(j_array.value("/0/string"_json_pointer, std::string("bar")) == "hello world");
|
||||
CHECK(j_array.value("/0/floating"_json_pointer, 12.34) == Approx(42.23));
|
||||
CHECK(j_array.value("/0/floating"_json_pointer, 12) == 42);
|
||||
CHECK(j_array.value("/0/object"_json_pointer, Json({{"foo", "bar"}})) == Json::object());
|
||||
CHECK(j_array.value("/0/array"_json_pointer, Json({10, 100})) == Json({1, 2, 3}));
|
||||
|
||||
CHECK(j_array_const.value("/0/integer"_json_pointer, 2) == 1);
|
||||
CHECK(j_array_const.value("/0/integer"_json_pointer, 1.0) == Approx(1));
|
||||
CHECK(j_array_const.value("/0/unsigned"_json_pointer, 2) == 1u);
|
||||
CHECK(j_array_const.value("/0/unsigned"_json_pointer, 1.0) == Approx(1u));
|
||||
CHECK(j_array_const.value("/0/boolean"_json_pointer, false) == true);
|
||||
CHECK(j_array_const.value("/0/string"_json_pointer, "bar") == "hello world");
|
||||
CHECK(j_array_const.value("/0/string"_json_pointer, std::string("bar")) == "hello world");
|
||||
CHECK(j_array_const.value("/0/floating"_json_pointer, 12.34) == Approx(42.23));
|
||||
CHECK(j_array_const.value("/0/floating"_json_pointer, 12) == 42);
|
||||
CHECK(j_array_const.value("/0/object"_json_pointer, Json({{"foo", "bar"}})) == Json::object());
|
||||
CHECK(j_array_const.value("/0/array"_json_pointer, Json({10, 100})) == Json({1, 2, 3}));
|
||||
|
||||
// Test out-of-range array index
|
||||
CHECK(j_array.value("/10"_json_pointer, 42) == 42);
|
||||
CHECK(j_array_const.value("/10"_json_pointer, 42) == 42);
|
||||
|
||||
// Test "-" index (append position is invalid)
|
||||
CHECK(j_array.value("/-"_json_pointer, 42) == 42);
|
||||
CHECK(j_array_const.value("/-"_json_pointer, 42) == 42);
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// 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&);
|
||||
|
||||
// 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&);
|
||||
CHECK_THROWS_WITH_AS(j_array_const.value("/01"_json_pointer, 1), "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", typename Json::parse_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user