From 2b29ca1812d0e2189abadeebc5c02ec27df57bdc Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 4 Oct 2026 12:04:35 +0200 Subject: [PATCH] Keep the serializer conversion for objects whose keys cannot be converted #5591 added a test converting nlohmann::json into a basic_json whose string type cannot be constructed from std::string. That instantiates convert_iteratively(), whose members.emplace_back(next.key(), ...) needs exactly that key conversion, and broke the build of unit-alt-string. Dispatch on the key's constructibility and leave such conversions to the serializers, as the levels above the nesting bound already do (#3425). Signed-off-by: Niels Lohmann --- include/nlohmann/json.hpp | 43 ++++++++++++++++++++++---------- single_include/nlohmann/json.hpp | 43 ++++++++++++++++++++++---------- 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 07b625a89..d877663dd 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1441,7 +1441,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec be destroyed. */ template - void convert_iteratively(const BasicJsonType& val) + void convert_iteratively(const BasicJsonType& val, std::true_type /*unused*/) { using other_const_iterator = typename BasicJsonType::const_iterator; @@ -1535,21 +1535,38 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (JSON_HEDLEY_LIKELY(guard.okay())) { - // every element comes back to the converting constructor - if (val.is_object()) - { - using other_object_t = typename BasicJsonType::object_t; - JSONSerializer::to_json(*this, val.template get_ref()); - } - else - { - using other_array_t = typename BasicJsonType::array_t; - JSONSerializer::to_json(*this, val.template get_ref()); - } + convert_by_serializers(val); return; } - convert_iteratively(val); + // the iterative conversion needs to construct this object type's keys + // from those of @a val; if it cannot, neither can the range constructor, + // and the serializers convert @a val some other way (see #3425) + convert_iteratively(val, std::is_constructible {}); + } + + /// @brief convert the object or array @a val with the serializers; every + /// element comes back to the converting constructor + template + void convert_by_serializers(const BasicJsonType& val) + { + if (val.is_object()) + { + using other_object_t = typename BasicJsonType::object_t; + JSONSerializer::to_json(*this, val.template get_ref()); + } + else + { + using other_array_t = typename BasicJsonType::array_t; + JSONSerializer::to_json(*this, val.template get_ref()); + } + } + + /// @brief convert @a val whose keys cannot be converted, see @ref convert_structured + template + void convert_iteratively(const BasicJsonType& val, std::false_type /*unused*/) + { + convert_by_serializers(val); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 16e87cc86..2b3a074ca 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -27978,7 +27978,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec be destroyed. */ template - void convert_iteratively(const BasicJsonType& val) + void convert_iteratively(const BasicJsonType& val, std::true_type /*unused*/) { using other_const_iterator = typename BasicJsonType::const_iterator; @@ -28072,21 +28072,38 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (JSON_HEDLEY_LIKELY(guard.okay())) { - // every element comes back to the converting constructor - if (val.is_object()) - { - using other_object_t = typename BasicJsonType::object_t; - JSONSerializer::to_json(*this, val.template get_ref()); - } - else - { - using other_array_t = typename BasicJsonType::array_t; - JSONSerializer::to_json(*this, val.template get_ref()); - } + convert_by_serializers(val); return; } - convert_iteratively(val); + // the iterative conversion needs to construct this object type's keys + // from those of @a val; if it cannot, neither can the range constructor, + // and the serializers convert @a val some other way (see #3425) + convert_iteratively(val, std::is_constructible {}); + } + + /// @brief convert the object or array @a val with the serializers; every + /// element comes back to the converting constructor + template + void convert_by_serializers(const BasicJsonType& val) + { + if (val.is_object()) + { + using other_object_t = typename BasicJsonType::object_t; + JSONSerializer::to_json(*this, val.template get_ref()); + } + else + { + using other_array_t = typename BasicJsonType::array_t; + JSONSerializer::to_json(*this, val.template get_ref()); + } + } + + /// @brief convert @a val whose keys cannot be converted, see @ref convert_structured + template + void convert_iteratively(const BasicJsonType& val, std::false_type /*unused*/) + { + convert_by_serializers(val); }