diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 2870a4f1d..75cb17557 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -411,6 +411,32 @@ inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ inline void from_json(const nlohmann::json& 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__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE +@since version 3.11.x +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE +@since version 3.11.x +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + // inspired from https://stackoverflow.com/a/26745591 // allows to call any std function as if (e.g. with begin): diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index dbbe68458..0dbabcc57 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2763,6 +2763,32 @@ JSON_HEDLEY_DIAGNOSTIC_POP inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ inline void from_json(const nlohmann::json& 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__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE +@since version 3.11.x +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE +@since version 3.11.x +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + // inspired from https://stackoverflow.com/a/26745591 // allows to call any std function as if (e.g. with begin): diff --git a/tests/src/unit-udt_macro.cpp b/tests/src/unit-udt_macro.cpp index ac9d9d2e8..bf25aa192 100644 --- a/tests/src/unit-udt_macro.cpp +++ b/tests/src/unit-udt_macro.cpp @@ -38,6 +38,26 @@ class person_with_private_data NLOHMANN_DEFINE_TYPE_INTRUSIVE(person_with_private_data, age, name, metadata) }; +class derived_person_with_private_data : public person_with_private_data +{ + private: + std::string hair_color{"blue"}; + + public: + bool operator==(const derived_person_with_private_data& rhs) const + { + return person_with_private_data::operator==(rhs) && hair_color == rhs.hair_color; + } + + derived_person_with_private_data() = default; + derived_person_with_private_data(std::string name_, int age_, json metadata_, std::string hair_color_) + : person_with_private_data(std::move(name_), age_, std::move(metadata_)) + , hair_color(std::move(hair_color_)) + {} + + NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(derived_person_with_private_data, person_with_private_data, hair_color) +}; + class person_with_private_data_2 { private: @@ -74,6 +94,31 @@ class person_with_private_data_2 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(person_with_private_data_2, age, name, metadata) }; +class derived_person_with_private_data_2 : public person_with_private_data_2 +{ + private: + std::string hair_color{"blue"}; + + public: + bool operator==(const derived_person_with_private_data_2& rhs) const + { + return person_with_private_data_2::operator==(rhs) && hair_color == rhs.hair_color; + } + + derived_person_with_private_data_2() = default; + derived_person_with_private_data_2(std::string name_, int age_, json metadata_, std::string hair_color_) + : person_with_private_data_2(std::move(name_), age_, std::move(metadata_)) + , hair_color(std::move(hair_color_)) + {} + + std::string getHairColor() const + { + return hair_color; + } + + NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(derived_person_with_private_data_2, person_with_private_data_2, hair_color) +}; + class person_without_private_data_1 { public: @@ -96,6 +141,26 @@ class person_without_private_data_1 NLOHMANN_DEFINE_TYPE_INTRUSIVE(person_without_private_data_1, age, name, metadata) }; +class derived_person_without_private_data_1 : public person_without_private_data_1 +{ + public: + std::string hair_color{"blue"}; + + public: + bool operator==(const derived_person_without_private_data_1& rhs) const + { + return person_without_private_data_1::operator==(rhs) && hair_color == rhs.hair_color; + } + + derived_person_without_private_data_1() = default; + derived_person_without_private_data_1(std::string name_, int age_, json metadata_, std::string hair_color_) + : person_without_private_data_1(std::move(name_), age_, std::move(metadata_)) + , hair_color(std::move(hair_color_)) + {} + + NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(derived_person_without_private_data_1, person_without_private_data_1, hair_color) +}; + class person_without_private_data_2 { public: @@ -118,6 +183,26 @@ class person_without_private_data_2 NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person_without_private_data_2, age, name, metadata) +class derived_person_without_private_data_2 : public person_without_private_data_2 +{ + public: + std::string hair_color{"blue"}; + + public: + bool operator==(const derived_person_without_private_data_2& rhs) const + { + return person_without_private_data_2::operator==(rhs) && hair_color == rhs.hair_color; + } + + derived_person_without_private_data_2() = default; + derived_person_without_private_data_2(std::string name_, int age_, json metadata_, std::string hair_color_) + : person_without_private_data_2(std::move(name_), age_, std::move(metadata_)) + , hair_color(std::move(hair_color_)) + {} +}; + +NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(derived_person_without_private_data_2, person_without_private_data_2, hair_color) + class person_without_private_data_3 { public: @@ -153,6 +238,31 @@ class person_without_private_data_3 NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(person_without_private_data_3, age, name, metadata) +class derived_person_without_private_data_3 : public person_without_private_data_3 +{ + public: + std::string hair_color{"blue"}; + + public: + bool operator==(const derived_person_without_private_data_3& rhs) const + { + return person_without_private_data_3::operator==(rhs) && hair_color == rhs.hair_color; + } + + derived_person_without_private_data_3() = default; + derived_person_without_private_data_3(std::string name_, int age_, json metadata_, std::string hair_color_) + : person_without_private_data_3(std::move(name_), age_, std::move(metadata_)) + , hair_color(std::move(hair_color_)) + {} + + std::string getHairColor() const + { + return hair_color; + } +}; + +NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(derived_person_without_private_data_3, person_without_private_data_3, hair_color) + class person_with_private_alphabet { public: @@ -216,6 +326,19 @@ class person_with_private_alphabet NLOHMANN_DEFINE_TYPE_INTRUSIVE(person_with_private_alphabet, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z) }; +class derived_person_with_private_alphabet : public person_with_private_alphabet +{ + public: + bool operator==(const derived_person_with_private_alphabet& other) const + { + return person_with_private_alphabet::operator==(other) && schwa == other.schwa; + } + + private: + int schwa = 0; + NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(derived_person_with_private_alphabet, person_with_private_alphabet, schwa) +}; + class person_with_public_alphabet { public: @@ -279,6 +402,18 @@ class person_with_public_alphabet NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person_with_public_alphabet, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z) +class derived_person_with_public_alphabet : public person_with_public_alphabet +{ + public: + bool operator==(const derived_person_with_public_alphabet& other) const + { + return person_with_public_alphabet::operator==(other) && schwa == other.schwa; + } + int schwa = 0; +}; + +NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(derived_person_with_public_alphabet, person_with_public_alphabet, schwa) + } // namespace persons TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_TYPE_INTRUSIVE and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE", T, @@ -307,6 +442,32 @@ TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_TYPE_INTRU } } +TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE and NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE", T, + persons::derived_person_with_private_data, + persons::derived_person_without_private_data_1, + persons::derived_person_without_private_data_2) +{ + SECTION("person") + { + // serialization + T p1("Erik", 1, {{"haircuts", 2}}, "red"); + CHECK(json(p1).dump() == "{\"age\":1,\"hair_color\":\"red\",\"metadata\":{\"haircuts\":2},\"name\":\"Erik\"}"); + + // deserialization + auto p2 = json(p1).get(); + CHECK(p2 == p1); + + // roundtrip + CHECK(T(json(p1)) == p1); + CHECK(json(T(json(p1))) == json(p1)); + + // check exception in case of missing field + json j = json(p1); + j.erase("age"); + CHECK_THROWS_WITH_AS(j.get(), "[json.exception.out_of_range.403] key 'age' not found", json::out_of_range); + } +} + TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT", T, persons::person_with_private_data_2, persons::person_without_private_data_3) @@ -341,6 +502,42 @@ TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_TYPE_INTRU } } +TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT and NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT", T, + persons::derived_person_with_private_data_2, + persons::derived_person_without_private_data_3) +{ + SECTION("derived person with default values") + { + // serialization of default constructed object + T p0; + CHECK(json(p0).dump() == "{\"age\":0,\"hair_color\":\"blue\",\"metadata\":null,\"name\":\"\"}"); + + // serialization + T p1("Erik", 1, {{"haircuts", 2}}, "red"); + CHECK(json(p1).dump() == "{\"age\":1,\"hair_color\":\"red\",\"metadata\":{\"haircuts\":2},\"name\":\"Erik\"}"); + + // deserialization + auto p2 = json(p1).get(); + CHECK(p2 == p1); + + // roundtrip + CHECK(T(json(p1)) == p1); + CHECK(json(T(json(p1))) == json(p1)); + + // check default value in case of missing field + json j = json(p1); + j.erase("name"); + j.erase("age"); + j.erase("metadata"); + j.erase("hair_color"); + T p3 = j.get(); + CHECK(p3.getName() == ""); + CHECK(p3.getAge() == 0); + CHECK(p3.getMetadata() == nullptr); + CHECK(p3.getHairColor() == "blue"); + } +} + TEST_CASE_TEMPLATE("Serialization/deserialization of classes with 26 public/private member variables via NLOHMANN_DEFINE_TYPE_INTRUSIVE and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE", T, persons::person_with_private_alphabet, persons::person_with_public_alphabet) @@ -412,3 +609,75 @@ TEST_CASE_TEMPLATE("Serialization/deserialization of classes with 26 public/priv } } } + +TEST_CASE_TEMPLATE("Serialization/deserialization of classes with 27 public/private member variables via NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE and NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE", T, + persons::derived_person_with_private_alphabet, + persons::derived_person_with_public_alphabet) +{ + SECTION("alphabet") + { + { + T obj1; + nlohmann::json const j = obj1; //via json object + T obj2; + j.get_to(obj2); + bool ok = (obj1 == obj2); + CHECK(ok); + } + + { + T obj1; + nlohmann::json const j1 = obj1; //via json string + std::string const s = j1.dump(); + nlohmann::json const j2 = nlohmann::json::parse(s); + T obj2; + j2.get_to(obj2); + bool ok = (obj1 == obj2); + CHECK(ok); + } + + { + T obj1; + nlohmann::json const j1 = obj1; //via msgpack + std::vector const buf = nlohmann::json::to_msgpack(j1); + nlohmann::json const j2 = nlohmann::json::from_msgpack(buf); + T obj2; + j2.get_to(obj2); + bool ok = (obj1 == obj2); + CHECK(ok); + } + + { + T obj1; + nlohmann::json const j1 = obj1; //via bson + std::vector const buf = nlohmann::json::to_bson(j1); + nlohmann::json const j2 = nlohmann::json::from_bson(buf); + T obj2; + j2.get_to(obj2); + bool ok = (obj1 == obj2); + CHECK(ok); + } + + { + T obj1; + nlohmann::json const j1 = obj1; //via cbor + std::vector const buf = nlohmann::json::to_cbor(j1); + nlohmann::json const j2 = nlohmann::json::from_cbor(buf); + T obj2; + j2.get_to(obj2); + bool ok = (obj1 == obj2); + CHECK(ok); + } + + { + T obj1; + nlohmann::json const j1 = obj1; //via ubjson + std::vector const buf = nlohmann::json::to_ubjson(j1); + nlohmann::json const j2 = nlohmann::json::from_ubjson(buf); + T obj2; + j2.get_to(obj2); + bool ok = (obj1 == obj2); + CHECK(ok); + } + } +}