Compare commits

..
Author SHA1 Message Date
Niels Lohmann 5a24b8c2d0 Fix element path for map/unordered_map JSON_DIAGNOSTICS errors
When converting a JSON array to std::map or std::unordered_map with a
non-string key, each element must itself be a [key, value] array. If an
element is not an array, from_json() threw type_error 302 with the outer
array's value (&j) as the exception context, so with JSON_DIAGNOSTICS
enabled the message pointed at the whole array instead of the offending
element (e.g. "(/outer/m)" instead of "(/outer/m/2)"), even though the
message text already described the element's type.

Both from_json() overloads now pass the element (&p) as the context, so
the reported JSON Pointer matches the type named in the message, the
same way std::vector<std::vector<T>> and similar conversions already do.

Fixes #5668.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 23:15:37 +02:00
4 changed files with 33 additions and 24 deletions
@@ -167,13 +167,9 @@ template<typename BasicJsonType, typename EnumType,
enable_if_t<std::is_enum<EnumType>::value, int> = 0>
inline void from_json(const BasicJsonType& j, EnumType& e)
{
using underlying_type = typename std::underlying_type<EnumType>::type;
// get_arithmetic_value() does not accept boolean_t; read the number that to_json() wrote instead
using value_type = typename std::conditional<std::is_same<underlying_type, typename BasicJsonType::boolean_t>::value,
typename BasicJsonType::number_unsigned_t, underlying_type>::type;
value_type val;
typename std::underlying_type<EnumType>::type val;
get_arithmetic_value(j, val);
e = static_cast<EnumType>(static_cast<underlying_type>(val));
e = static_cast<EnumType>(val);
}
#endif // JSON_DISABLE_ENUM_SERIALIZATION
@@ -568,7 +564,7 @@ inline void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allo
{
if (JSON_HEDLEY_UNLIKELY(!p.is_array()))
{
JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &j));
JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p));
}
m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
}
@@ -588,7 +584,7 @@ inline void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Has
{
if (JSON_HEDLEY_UNLIKELY(!p.is_array()))
{
JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &j));
JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p));
}
m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
}
+4 -8
View File
@@ -5664,13 +5664,9 @@ template<typename BasicJsonType, typename EnumType,
enable_if_t<std::is_enum<EnumType>::value, int> = 0>
inline void from_json(const BasicJsonType& j, EnumType& e)
{
using underlying_type = typename std::underlying_type<EnumType>::type;
// get_arithmetic_value() does not accept boolean_t; read the number that to_json() wrote instead
using value_type = typename std::conditional<std::is_same<underlying_type, typename BasicJsonType::boolean_t>::value,
typename BasicJsonType::number_unsigned_t, underlying_type>::type;
value_type val;
typename std::underlying_type<EnumType>::type val;
get_arithmetic_value(j, val);
e = static_cast<EnumType>(static_cast<underlying_type>(val));
e = static_cast<EnumType>(val);
}
#endif // JSON_DISABLE_ENUM_SERIALIZATION
@@ -6065,7 +6061,7 @@ inline void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allo
{
if (JSON_HEDLEY_UNLIKELY(!p.is_array()))
{
JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &j));
JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p));
}
m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
}
@@ -6085,7 +6081,7 @@ inline void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Has
{
if (JSON_HEDLEY_UNLIKELY(!p.is_array()))
{
JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &j));
JSON_THROW(type_error::create(302, concat("type must be array, but is ", p.type_name()), &p));
}
m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
}
-8
View File
@@ -1358,14 +1358,6 @@ TEST_CASE("value conversion")
CHECK(json(value_1).get<c_enum>() == value_1);
CHECK(json(cpp_enum::value_1).get<cpp_enum>() == cpp_enum::value_1);
}
SECTION("get an enum with underlying type bool (#5671)")
{
enum class bool_enum : bool { off, on };
CHECK(json(bool_enum::off).get<bool_enum>() == bool_enum::off);
CHECK(json(bool_enum::on).get<bool_enum>() == bool_enum::on);
}
#endif
SECTION("more involved conversions")
+25
View File
@@ -17,6 +17,9 @@
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <map>
#include <unordered_map>
TEST_CASE("Better diagnostics")
{
SECTION("empty JSON Pointer")
@@ -331,6 +334,28 @@ TEST_CASE("Regression tests for extended diagnostics")
}
}
SECTION("Regression test for issue #5668 - wrong path for std::map/unordered_map with non-string keys")
{
// a map with non-string keys is read from an array of [key, value] arrays;
// element 2 of "m" is not an array, so the path must point at "m/2", not "m"
json j;
j["outer"]["m"] = json::array({json::array({1, 2}), json::array({3, 4}), 5});
SECTION("std::map")
{
CHECK_THROWS_WITH_AS((j["outer"]["m"].get<std::map<int, int>>()),
"[json.exception.type_error.302] (/outer/m/2) type must be array, "
"but is number", json::type_error);
}
SECTION("std::unordered_map")
{
CHECK_THROWS_WITH_AS((j["outer"]["m"].get<std::unordered_map<int, int>>()),
"[json.exception.type_error.302] (/outer/m/2) type must be array, "
"but is number", json::type_error);
}
}
SECTION("Regression test - swap(array_t&)/swap(object_t&) must update JSON_DIAGNOSTICS parent pointers")
{
// swap(array_t&)