Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions docs/mkdocs/docs/home/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@

yield different results (`#!json [true]` vs. `#!json true`)?

This is a known issue, and -- even worse -- the behavior differs between GCC and Clang. The "culprit" for this is the library's constructor overloads for initializer lists to allow syntax like
Starting from this version, single-value brace initialization is treated as copy/move instead of wrapping in a single-element array:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change we cannot accept. Please revert.


```cpp
json j_orig = {{"key", "value"}};

json j{j_orig};
j_orig.method();
```

The root cause was the library's constructor overloads for initializer lists, which allow syntax like

```cpp
json array = {1, 2, 3, 4};
Expand All @@ -32,11 +41,13 @@ for arrays and
json object = {{"one", 1}, {"two", 2}};
```

for objects.
for objects. Because C++ always prefers `initializer_list` constructors for brace initialization, a single-element brace-init `json j{someValue}` previously wrapped the value in an array instead of copying it.

!!! tip
After the fix, a single-element brace-init behaves like copy/move initialization. To explicitly create a single-element array, use `json::array({value})`.

!!! note

To avoid any confusion and ensure portable code, **do not** use brace initialization with the types `basic_json`, `json`, or `ordered_json` unless you want to create an object or array as shown in the examples above.
The fix changes the behavior of single-element brace initialization: `json j{x}` is now equivalent to `json j(x)` (copy/move) rather than `json j = json::array({x})` (single-element array). Code relying on the old behavior should be updated to use `json::array({x})` explicitly.

## Limitations

Expand Down
10 changes: 3 additions & 7 deletions include/nlohmann/detail/conversions/from_json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -386,14 +386,10 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)

ConstructibleObjectType ret;
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
using value_type = typename ConstructibleObjectType::value_type;
std::transform(
inner_object->begin(), inner_object->end(),
std::inserter(ret, ret.begin()),
[](typename BasicJsonType::object_t::value_type const & p)
for (const auto& p : *inner_object)
{
return value_type(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
});
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
}
obj = std::move(ret);
}

Expand Down
12 changes: 12 additions & 0 deletions include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
else
{
// if there is exactly one element and type deduction is enabled,
// treat it as copy/move to avoid unintentionally wrapping a single
// json value in a single-element array
// (see https://github.com/nlohmann/json/issues/5074)
if (type_deduction && init.size() == 1)
{
*this = init.begin()->moved_or_copied();
set_parents();
assert_invariant();
return;
}

// the initializer list describes an array -> create an array
m_data.m_type = value_t::array;
m_data.m_value.array = create<array_t>(init.begin(), init.end());
Expand Down
148 changes: 78 additions & 70 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3499,71 +3499,71 @@ NLOHMANN_JSON_NAMESPACE_END
// SPDX-License-Identifier: MIT

#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_

#include <cstdint> // int64_t, uint64_t
#include <map> // map
#include <memory> // allocator
#include <string> // string
#include <vector> // vector
#include <cstdint> // int64_t, uint64_t
#include <map> // map
#include <memory> // allocator
#include <string> // string
#include <vector> // vector

// #include <nlohmann/detail/abi_macros.hpp>
// #include <nlohmann/detail/abi_macros.hpp>


/*!
@brief namespace for Niels Lohmann
@see https://github.com/nlohmann
@since version 1.0.0
*/
NLOHMANN_JSON_NAMESPACE_BEGIN
/*!
@brief namespace for Niels Lohmann
@see https://github.com/nlohmann
@since version 1.0.0
*/
NLOHMANN_JSON_NAMESPACE_BEGIN

/*!
@brief default JSONSerializer template argument
/*!
@brief default JSONSerializer template argument

This serializer ignores the template arguments and uses ADL
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization.
*/
template<typename T = void, typename SFINAE = void>
struct adl_serializer;

/// a class to store JSON values
/// @sa https://json.nlohmann.me/api/basic_json/
template<template<typename U, typename V, typename... Args> class ObjectType =
std::map,
template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer,
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
class CustomBaseClass = void>
class basic_json;

/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType>
class json_pointer;
This serializer ignores the template arguments and uses ADL
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization.
*/
template<typename T = void, typename SFINAE = void>
struct adl_serializer;

/// a class to store JSON values
/// @sa https://json.nlohmann.me/api/basic_json/
template<template<typename U, typename V, typename... Args> class ObjectType =
std::map,
template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer,
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
class CustomBaseClass = void>
class basic_json;

/*!
@brief default specialization
@sa https://json.nlohmann.me/api/json/
*/
using json = basic_json<>;
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType>
class json_pointer;

/// @brief a minimal map-like container that preserves insertion order
/// @sa https://json.nlohmann.me/api/ordered_map/
template<class Key, class T, class IgnoredLess, class Allocator>
struct ordered_map;
/*!
@brief default specialization
@sa https://json.nlohmann.me/api/json/
*/
using json = basic_json<>;

/// @brief a minimal map-like container that preserves insertion order
/// @sa https://json.nlohmann.me/api/ordered_map/
template<class Key, class T, class IgnoredLess, class Allocator>
struct ordered_map;

/// @brief specialization that maintains the insertion order of object keys
/// @sa https://json.nlohmann.me/api/ordered_json/
using ordered_json = basic_json<nlohmann::ordered_map>;
/// @brief specialization that maintains the insertion order of object keys
/// @sa https://json.nlohmann.me/api/ordered_json/
using ordered_json = basic_json<nlohmann::ordered_map>;

NLOHMANN_JSON_NAMESPACE_END
NLOHMANN_JSON_NAMESPACE_END

#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_

Expand Down Expand Up @@ -5219,14 +5219,10 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)

ConstructibleObjectType ret;
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
using value_type = typename ConstructibleObjectType::value_type;
std::transform(
inner_object->begin(), inner_object->end(),
std::inserter(ret, ret.begin()),
[](typename BasicJsonType::object_t::value_type const & p)
for (const auto& p : *inner_object)
{
return value_type(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
});
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
}
obj = std::move(ret);
}

Expand Down Expand Up @@ -5427,7 +5423,7 @@ NLOHMANN_JSON_NAMESPACE_END


// #include <nlohmann/detail/macro_scope.hpp>
// JSON_HAS_CPP_17
// JSON_HAS_CPP_17
#ifdef JSON_HAS_CPP_17
#include <optional> // optional
#endif
Expand Down Expand Up @@ -20255,10 +20251,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
const bool allow_exceptions = true,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false
)
)
{
return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
}

private:
Expand Down Expand Up @@ -20956,8 +20952,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
detail::enable_if_t <
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
std::forward<CompatibleType>(val))))
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
std::forward<CompatibleType>(val))))
{
JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));
set_parents();
Expand Down Expand Up @@ -21074,6 +21070,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
else
{
// if there is exactly one element and type deduction is enabled,
// treat it as copy/move to avoid unintentionally wrapping a single
// json value in a single-element array
// (see https://github.com/nlohmann/json/issues/5074)
if (type_deduction && init.size() == 1)
{
*this = init.begin()->moved_or_copied();
set_parents();
assert_invariant();
return;
}

// the initializer list describes an array -> create an array
m_data.m_type = value_t::array;
m_data.m_value.array = create<array_t>(init.begin(), init.end());
Expand Down Expand Up @@ -21751,7 +21759,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
detail::has_from_json<basic_json_t, ValueType>::value,
int > = 0 >
ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept(
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>())))
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>())))
{
auto ret = ValueType();
JSONSerializer<ValueType>::from_json(*this, ret);
Expand Down Expand Up @@ -21793,7 +21801,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
detail::has_non_default_from_json<basic_json_t, ValueType>::value,
int > = 0 >
ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept(
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>())))
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>())))
{
return JSONSerializer<ValueType>::from_json(*this);
}
Expand Down Expand Up @@ -21943,7 +21951,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
detail::has_from_json<basic_json_t, ValueType>::value,
int > = 0 >
ValueType & get_to(ValueType& v) const noexcept(noexcept(
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v)))
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v)))
{
JSONSerializer<ValueType>::from_json(*this, v);
return v;
Expand Down
2 changes: 1 addition & 1 deletion tests/src/unit-class_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1506,7 +1506,7 @@ TEST_CASE("parser class")

// removed all objects in array.
CHECK (j_filtered2.size() == 1);
CHECK (j_filtered2 == json({1}));
CHECK (j_filtered2 == json::array({1}));
}

SECTION("filter specific events")
Expand Down
2 changes: 1 addition & 1 deletion tests/src/unit-class_parser_diagnostic_positions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1517,7 +1517,7 @@ TEST_CASE("parser class")

// removed all objects in array.
CHECK (j_filtered2.size() == 1);
CHECK (j_filtered2 == json({1}));
CHECK (j_filtered2 == json::array({1}));
}

SECTION("filter specific events")
Expand Down
Loading
Loading