diff --git a/docs/mkdocs/docs/api/basic_json/basic_json.md b/docs/mkdocs/docs/api/basic_json/basic_json.md index 6aaab23c9..24c0923ba 100644 --- a/docs/mkdocs/docs/api/basic_json/basic_json.md +++ b/docs/mkdocs/docs/api/basic_json/basic_json.md @@ -272,6 +272,15 @@ basic_json(basic_json&& other) noexcept; When used without parentheses around an empty initializer list, `basic_json()` is called instead of this function, yielding the JSON `#!json null` value. +- Overload 4: + + !!! info "Implicit conversion" + + The conversion is implicit unless [`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md) + is defined to `0` and `BasicJsonType::string_t` differs from `string_t`. In that case, the constructor is + `explicit`, so a JSON value with a different string type is no longer silently converted, for example when it is + passed to a function taking `#!cpp const json&`. Write `#!cpp json(other)` or `#!cpp other.get()` instead. + - Overload 7: !!! info "Preconditions" @@ -420,7 +429,8 @@ basic_json(basic_json&& other) noexcept; 1. Since version 1.0.0. 2. Since version 1.0.0. 3. Since version 2.1.0. -4. Since version 3.2.0. +4. Since version 3.2.0. Explicit for different string types if `JSON_USE_IMPLICIT_CONVERSIONS` is `0` since + version 3.13.0. 5. Since version 1.0.0. 6. Since version 1.0.0. 7. Since version 1.0.0. diff --git a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md index e3d5fb29d..c6aded55d 100644 --- a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md +++ b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md @@ -5,7 +5,9 @@ ``` When defined to `0`, implicit conversions are switched off. By default, implicit conversions are switched on. The -value directly affects [`operator ValueType`](../basic_json/operator_ValueType.md). +value directly affects [`operator ValueType`](../basic_json/operator_ValueType.md) and the +[converting constructor](../basic_json/basic_json.md) from a `basic_json` specialization with a different string +type (overload 4). ## Default definition @@ -57,6 +59,25 @@ By default, implicit conversions are enabled. auto s = j.get(); ``` +??? example "Conversion between `basic_json` specializations" + + A `basic_json` specialization with a different string type is also no longer converted implicitly when + `JSON_USE_IMPLICIT_CONVERSIONS` is defined to `0`: + + ```cpp + using wjson = nlohmann::basic_json; + + void load(const nlohmann::json& j); + + wjson wj = /* ... */; + load(wj); // error: no implicit conversion + load(nlohmann::json(wj)); // OK: explicit conversion + load(wj.get()); // OK: explicit conversion + ``` + + Specializations that share the same string type, such as `json` and `ordered_json`, remain implicitly + convertible. + ## See also - [**operator ValueType**](../basic_json/operator_ValueType.md) - get a value (implicit) @@ -66,3 +87,4 @@ By default, implicit conversions are enabled. ## Version history - Added in version 3.9.0. +- Also affects the conversion between `basic_json` specializations with different string types since version 3.13.0. diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index abd461d6c..d930cf444 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1605,12 +1605,42 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec assert_invariant(); } + private: + /// whether a basic_json specialization can be converted implicitly into this one; + /// with JSON_USE_IMPLICIT_CONVERSIONS set to 0, this is only the case if both share + /// the same string type (see https://github.com/nlohmann/json/issues/2649) + template + using is_implicitly_convertible_basic_json = std::integral_constant < bool, + static_cast(JSON_USE_IMPLICIT_CONVERSIONS) + || std::is_same::value >; + + /// tag to select the constructor that performs the conversion from another basic_json specialization + struct convert_basic_json_tag {}; + + public: /// @brief create a JSON value from an existing one /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ template < typename BasicJsonType, detail::enable_if_t < - detail::is_basic_json::value&& !std::is_same::value, int > = 0 > + detail::is_basic_json::value&& !std::is_same::value + && is_implicitly_convertible_basic_json::value, int > = 0 > basic_json(const BasicJsonType& val) + : basic_json(val, convert_basic_json_tag{}) + {} + + /// @brief create a JSON value from an existing one + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + template < typename BasicJsonType, + detail::enable_if_t < + detail::is_basic_json::value&& !std::is_same::value + && !is_implicitly_convertible_basic_json::value, int > = 0 > + explicit basic_json(const BasicJsonType& val) + : basic_json(val, convert_basic_json_tag{}) + {} + + private: + template + basic_json(const BasicJsonType& val, convert_basic_json_tag /*unused*/) #if JSON_DIAGNOSTIC_POSITIONS : start_position(val.start_pos()), end_position(val.end_pos()) @@ -1666,6 +1696,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec assert_invariant(); } + public: /// @brief create a container (array or object) from an initializer list /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(initializer_list_t init, @@ -2432,7 +2463,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec int > = 0 > BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const { - return *this; + return BasicJsonType(*this); } /*! diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 81ddfb1c4..0231d4694 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -26486,12 +26486,42 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec assert_invariant(); } + private: + /// whether a basic_json specialization can be converted implicitly into this one; + /// with JSON_USE_IMPLICIT_CONVERSIONS set to 0, this is only the case if both share + /// the same string type (see https://github.com/nlohmann/json/issues/2649) + template + using is_implicitly_convertible_basic_json = std::integral_constant < bool, + static_cast(JSON_USE_IMPLICIT_CONVERSIONS) + || std::is_same::value >; + + /// tag to select the constructor that performs the conversion from another basic_json specialization + struct convert_basic_json_tag {}; + + public: /// @brief create a JSON value from an existing one /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ template < typename BasicJsonType, detail::enable_if_t < - detail::is_basic_json::value&& !std::is_same::value, int > = 0 > + detail::is_basic_json::value&& !std::is_same::value + && is_implicitly_convertible_basic_json::value, int > = 0 > basic_json(const BasicJsonType& val) + : basic_json(val, convert_basic_json_tag{}) + {} + + /// @brief create a JSON value from an existing one + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + template < typename BasicJsonType, + detail::enable_if_t < + detail::is_basic_json::value&& !std::is_same::value + && !is_implicitly_convertible_basic_json::value, int > = 0 > + explicit basic_json(const BasicJsonType& val) + : basic_json(val, convert_basic_json_tag{}) + {} + + private: + template + basic_json(const BasicJsonType& val, convert_basic_json_tag /*unused*/) #if JSON_DIAGNOSTIC_POSITIONS : start_position(val.start_pos()), end_position(val.end_pos()) @@ -26547,6 +26577,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec assert_invariant(); } + public: /// @brief create a container (array or object) from an initializer list /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(initializer_list_t init, @@ -27313,7 +27344,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec int > = 0 > BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const { - return *this; + return BasicJsonType(*this); } /*! diff --git a/tests/src/unit-alt-string.cpp b/tests/src/unit-alt-string.cpp index ec2ac146b..55c1d754f 100644 --- a/tests/src/unit-alt-string.cpp +++ b/tests/src/unit-alt-string.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -373,4 +374,34 @@ TEST_CASE("alternative string type") const auto j2 = j.flatten(); CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})"); } + + SECTION("conversion between basic_json specializations (#2649)") + { + // explicit conversions are always possible + CHECK(std::is_constructible::value); + CHECK(std::is_constructible::value); + CHECK(std::is_constructible::value); + CHECK(std::is_constructible::value); + + // specializations with the same string type are implicitly convertible + CHECK(std::is_convertible::value); + CHECK(std::is_convertible::value); + + // specializations with different string types are only implicitly convertible + // if implicit conversions are enabled +#if JSON_USE_IMPLICIT_CONVERSIONS + CHECK(std::is_convertible::value); + CHECK(std::is_convertible::value); +#else + CHECK_FALSE(std::is_convertible::value); + CHECK_FALSE(std::is_convertible::value); +#endif + + // get() works in either case + const nlohmann::json j = {{"foo", 1}, {"bar", true}}; + CHECK(j.get() == nlohmann::ordered_json(j)); + // (only a number is converted here, as objects and strings are affected by #3425) + CHECK(nlohmann::json(42).get() == 42); + CHECK(alt_json(nlohmann::json(42)) == 42); + } }