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
46 changes: 26 additions & 20 deletions include/nlohmann/detail/macro_scope.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@
#define JSON_HAS_CPP_11
#endif

// Since C++20 the preprocessor supports __VA_OPT__(x) which expands to x only
// when __VA_ARGS__ is non-empty. We use this in NLOHMANN_DEFINE_TYPE_* macros
// to avoid generating invalid code when no member arguments are passed.
// On pre-C++20 compilers __VA_OPT__ is unavailable; zero-member invocations
// were already ill-formed on those compilers, so there is no regression.

#ifdef __has_include
#if __has_include(<version>)
#include <version>
Expand Down Expand Up @@ -416,9 +422,9 @@
*/
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__))) } \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__))) }

/*!
@brief macro
Expand All @@ -428,9 +434,9 @@
*/
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__))) } \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { __VA_OPT__(const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__))) }

/*!
@brief macro
Expand All @@ -440,7 +446,7 @@
*/
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }
friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__))) }

/*!
@brief macro
Expand All @@ -450,9 +456,9 @@
*/
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__))) } \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__))) }

/*!
@brief macro
Expand All @@ -462,9 +468,9 @@
*/
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__))) } \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { __VA_OPT__(const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__))) }

/*!
@brief macro
Expand All @@ -474,7 +480,7 @@
*/
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }
void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__))) }

/*!
@brief macro
Expand All @@ -484,9 +490,9 @@
*/
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__))) } \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__))) }

/*!
@brief macro
Expand All @@ -496,9 +502,9 @@
*/
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType&>(nlohmann_json_t)); __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__))) } \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); __VA_OPT__(const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__))) }

/*!
@brief macro
Expand All @@ -508,7 +514,7 @@
*/
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }
friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__))) }

/*!
@brief macro
Expand All @@ -518,9 +524,9 @@
*/
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__))) } \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }
void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__))) }

/*!
@brief macro
Expand All @@ -530,9 +536,9 @@
*/
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__))) } \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); __VA_OPT__(const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__))) }

/*!
@brief macro
Expand All @@ -542,7 +548,7 @@
*/
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }
void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__))) }

// inspired from https://stackoverflow.com/a/26745591
// allows calling any std function as if (e.g., with begin):
Expand Down
59 changes: 59 additions & 0 deletions test_empty_macro.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Standalone compile test for issue #4041
// Tests that zero-member NLOHMANN_DEFINE_TYPE_* macros compile and work correctly.
#include <cassert>
#include <string>
#include <nlohmann/json.hpp>

// --- Intrusive (friend functions inside the class) ---
struct EmptyIntrusive
{
bool operator==(const EmptyIntrusive&) const { return true; }
NLOHMANN_DEFINE_TYPE_INTRUSIVE(EmptyIntrusive)
};

struct EmptyIntrusiveWithDefault
{
bool operator==(const EmptyIntrusiveWithDefault&) const { return true; }
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(EmptyIntrusiveWithDefault)
};

struct EmptyIntrusiveOnlySerialize
{
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(EmptyIntrusiveOnlySerialize)
};

// --- Non-intrusive (free functions in the same namespace) ---
struct EmptyNonIntrusive
{
bool operator==(const EmptyNonIntrusive&) const { return true; }
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(EmptyNonIntrusive)

struct EmptyNonIntrusiveWithDefault
{
bool operator==(const EmptyNonIntrusiveWithDefault&) const { return true; }
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(EmptyNonIntrusiveWithDefault)

struct EmptyNonIntrusiveOnlySerialize {};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(EmptyNonIntrusiveOnlySerialize)

int main()
{
// to_json produces {}
assert(nlohmann::json(EmptyIntrusive{}).dump() == "{}");
assert(nlohmann::json(EmptyIntrusiveWithDefault{}).dump() == "{}");
assert(nlohmann::json(EmptyIntrusiveOnlySerialize{}).dump() == "{}");
assert(nlohmann::json(EmptyNonIntrusive{}).dump() == "{}");
assert(nlohmann::json(EmptyNonIntrusiveWithDefault{}).dump() == "{}");
assert(nlohmann::json(EmptyNonIntrusiveOnlySerialize{}).dump() == "{}");

// from_json round-trips successfully
nlohmann::json j = nlohmann::json::object();
auto e1 = j.get<EmptyIntrusive>(); (void)e1;
auto e2 = j.get<EmptyIntrusiveWithDefault>(); (void)e2;
auto e3 = j.get<EmptyNonIntrusive>(); (void)e3;
auto e4 = j.get<EmptyNonIntrusiveWithDefault>(); (void)e4;

return 0;
}
93 changes: 93 additions & 0 deletions tests/src/unit-udt_macro.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,43 @@ class derived_person_only_serialize_private : person_without_default_constructor
NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(derived_person_only_serialize_private, person_without_default_constructor_1, hair_color)
};

// Zero-member types for issue #4041 regression: macros must compile and
// produce valid (empty) JSON objects when no member arguments are given.
struct empty_type_intrusive
{
bool operator==(const empty_type_intrusive& /*rhs*/) const { return true; }
NLOHMANN_DEFINE_TYPE_INTRUSIVE(empty_type_intrusive)
};

struct empty_type_intrusive_with_default
{
bool operator==(const empty_type_intrusive_with_default& /*rhs*/) const { return true; }
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(empty_type_intrusive_with_default)
};

struct empty_type_intrusive_only_serialize
{
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(empty_type_intrusive_only_serialize)
};

struct empty_type_non_intrusive
{
bool operator==(const empty_type_non_intrusive& /*rhs*/) const { return true; }
};
// NOLINTNEXTLINE(misc-use-internal-linkage)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(empty_type_non_intrusive)

struct empty_type_non_intrusive_with_default
{
bool operator==(const empty_type_non_intrusive_with_default& /*rhs*/) const { return true; }
};
// NOLINTNEXTLINE(misc-use-internal-linkage)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(empty_type_non_intrusive_with_default)

struct empty_type_non_intrusive_only_serialize {};
// NOLINTNEXTLINE(misc-use-internal-linkage)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(empty_type_non_intrusive_only_serialize)

} // namespace persons

TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_TYPE_INTRUSIVE and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE", Pair, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
Expand Down Expand Up @@ -707,3 +744,59 @@ TEST_CASE_TEMPLATE("Serialization of non-default-constructible classes via NLOHM
R"([{"age":1,"hair_color":"brown","name":"Erik"},{"age":2,"hair_color":"black","name":"Kyle"}])"));
}
}

// Regression test for issue #4041: zero-member NLOHMANN_DEFINE_TYPE_* macros
// must compile and produce valid (empty) JSON objects.
TEST_CASE_TEMPLATE("Zero-member NLOHMANN_DEFINE_TYPE_* macros produce empty JSON objects (issue #4041)", Json, // NOLINT
nlohmann::json, nlohmann::ordered_json)
{
SECTION("NLOHMANN_DEFINE_TYPE_INTRUSIVE with zero members")
{
persons::empty_type_intrusive obj{};
Json j = obj;
CHECK(j.dump() == "{}");
persons::empty_type_intrusive obj2 = j.template get<persons::empty_type_intrusive>();
CHECK(obj2 == obj);
}

SECTION("NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT with zero members")
{
persons::empty_type_intrusive_with_default obj{};
Json j = obj;
CHECK(j.dump() == "{}");
persons::empty_type_intrusive_with_default obj2 = j.template get<persons::empty_type_intrusive_with_default>();
CHECK(obj2 == obj);
}

SECTION("NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE with zero members")
{
persons::empty_type_intrusive_only_serialize obj{};
Json j = obj;
CHECK(j.dump() == "{}");
}

SECTION("NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE with zero members")
{
persons::empty_type_non_intrusive obj{};
Json j = obj;
CHECK(j.dump() == "{}");
persons::empty_type_non_intrusive obj2 = j.template get<persons::empty_type_non_intrusive>();
CHECK(obj2 == obj);
}

SECTION("NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT with zero members")
{
persons::empty_type_non_intrusive_with_default obj{};
Json j = obj;
CHECK(j.dump() == "{}");
persons::empty_type_non_intrusive_with_default obj2 = j.template get<persons::empty_type_non_intrusive_with_default>();
CHECK(obj2 == obj);
}

SECTION("NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE with zero members")
{
persons::empty_type_non_intrusive_only_serialize obj{};
Json j = obj;
CHECK(j.dump() == "{}");
}
}