From c3dc381425b1cbd3cb58d5f53b34d89bf6556a7a Mon Sep 17 00:00:00 2001 From: Martchus Date: Sun, 3 Nov 2019 22:02:56 +0100 Subject: [PATCH] Support `std::variant` --- CMakeLists.txt | 2 +- README.md | 4 ++ lib/binary/reflector.h | 49 ++++++++++++++++- lib/json/errorhandling.h | 8 +++ lib/json/reflector.h | 101 +++++++++++++++++++++++++++++++++- lib/tests/binaryreflector.cpp | 45 +++++++++++++++ lib/tests/jsonreflector.cpp | 32 ++++++++--- lib/traits.h | 2 + 8 files changed, 231 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 47b53cf..1f196ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ set(META_APP_CATEGORIES "Utility;") set(META_GUI_OPTIONAL false) set(META_VERSION_MAJOR 0) set(META_VERSION_MINOR 0) -set(META_VERSION_PATCH 10) +set(META_VERSION_PATCH 11) set(META_APP_VERSION ${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH}) set(META_CXX_STANDARD 17) set(META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION ON) diff --git a/README.md b/README.md index 9e2d29f..6850605 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ The following table shows the mapping of supported C++ types to supported JSON t | `std::tuple` | array | | `std::unique_ptr`, `std::shared_ptr` | depends/null | | `std::map`, `std::unordered_map` | object | +| `std::variant` | object | | `JsonSerializable` | object | ### Remarks @@ -85,6 +86,9 @@ The following table shows the mapping of supported C++ types to supported JSON t `REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH`, `REFLECTIVE_RAPIDJSON_TREAT_AS_SET` or `REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_SET`. * The key type of `std::map` and `std::unordered_map` must be `std::string`. +* An `std::variant` is represented by an object like `{"index": ..., "data": ...}` where `index` is the + zero-based index of the alternative held by the variant and `data` the value held by the variant. The + type of `data` is `null` for `std::monostate` and otherwise deduced as usual. * For custom (de)serialization, see the section below. * The binary (de)serializer supports approximately the same C++ types but obviously maps them to a platform independent binary representation rather than a JSON type. diff --git a/lib/binary/reflector.h b/lib/binary/reflector.h index 6b617be..50026e9 100644 --- a/lib/binary/reflector.h +++ b/lib/binary/reflector.h @@ -18,6 +18,7 @@ #include #include #include +#include namespace ReflectiveRapidJSON { @@ -40,7 +41,7 @@ namespace BinaryReflector { template using IsBuiltInType = Traits::Any, - Traits::IsIteratable, Traits::IsSpecializingAnyOf, std::is_enum>; + Traits::IsIteratable, Traits::IsSpecializingAnyOf, std::is_enum, IsVariant>; template using IsCustomType = Traits::Not>; class BinaryDeserializer; @@ -63,7 +64,8 @@ public: Traits::EnableIf, Traits::None, IsMultiMapOrHash, Traits::All, Traits::IsResizable>>> * = nullptr> void read(Type &iteratable); - template > * = nullptr> void read(Type &customType); + template > * = nullptr> void read(Type &enumValue); + template > * = nullptr> void read(Type &variant); template > * = nullptr> void read(Type &customType); std::unordered_map m_pointer; @@ -78,7 +80,8 @@ public: template > * = nullptr> void write(const Type &pointer); template > * = nullptr> void write(const Type &pointer); template , Traits::HasSize> * = nullptr> void write(const Type &iteratable); - template > * = nullptr> void write(const Type &customType); + template > * = nullptr> void write(const Type &enumValue); + template > * = nullptr> void write(const Type &variant); template > * = nullptr> void write(const Type &customType); std::unordered_map m_pointer; @@ -168,6 +171,33 @@ template > *> void BinaryDese enumValue = static_cast(value); } +/// \cond +namespace Detail { +template +void readVariantValueByRuntimeIndex(std::size_t runtimeIndex, Variant &variant, BinaryDeserializer &deserializer) +{ + if constexpr (compiletimeIndex < std::variant_size_v) { + if (compiletimeIndex == runtimeIndex) { + if constexpr (std::is_same_v, std::monostate>) { + variant = std::monostate{}; + } else { + deserializer.read(variant.template emplace()); + } + } else { + readVariantValueByRuntimeIndex(runtimeIndex, variant, deserializer); + } + } else { + throw CppUtilities::ConversionException("Variant index is out of expected range"); + } +} +} // namespace Detail +/// \endcond + +template > *> void BinaryDeserializer::read(Type &variant) +{ + Detail::readVariantValueByRuntimeIndex(readByte(), variant, *this); +} + template > *> void BinaryDeserializer::read(Type &customType) { readCustomType(*this, customType); @@ -232,6 +262,19 @@ template > *> void BinarySeri write(static_cast::type>(enumValue)); } +template > *> void BinarySerializer::write(const Type &variant) +{ + static_assert(std::variant_size_v < std::numeric_limits::max(), "index will not exceed limit"); + writeByte(static_cast(variant.index())); + std::visit( + [this](const auto &valueOfActualType) { + if constexpr (!std::is_same_v, std::monostate>) { + write(valueOfActualType); + } + }, + variant); +} + template > *> void BinarySerializer::write(const Type &customType) { writeCustomType(*this, customType); diff --git a/lib/json/errorhandling.h b/lib/json/errorhandling.h index 9f41c6f..fc42a8d 100644 --- a/lib/json/errorhandling.h +++ b/lib/json/errorhandling.h @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace ReflectiveRapidJSON { @@ -26,6 +27,8 @@ enum class JsonDeserializationErrorKind : std::uint8_t { ArraySizeMismatch, /**< The expected array size does not match the actual size of the JSON array. A fixed size is expected when deserializing an std::tuple. */ ConversionError, /**< The expected type matches the type present in the JSON document, but further conversion of the value failed. */ UnexpectedDuplicate, /**< The expected type matches the type present in the JSON document, but the value can not be added to the container because it is already present and duplicates are not allowed. */ + InvalidVariantObject, /**< The present object is supposed to represent an std::variant but lacks the index or data member. */ + InvalidVariantIndex, /**< The present variant index is not a number of outside of the expected range. */ }; /*! @@ -50,6 +53,11 @@ constexpr JsonType jsonType() return JsonType::Number; } +template > * = nullptr> constexpr JsonType jsonType() +{ + return JsonType::Null; +} + template > * = nullptr> constexpr JsonType jsonType() { return JsonType::Bool; diff --git a/lib/json/reflector.h b/lib/json/reflector.h index 84bf1a8..e1f1a9b 100644 --- a/lib/json/reflector.h +++ b/lib/json/reflector.h @@ -18,6 +18,7 @@ #include #include #include +#include #include "./errorhandling.h" @@ -75,7 +76,7 @@ inline RAPIDJSON_NAMESPACE::Document parseJsonDocFromString(const char *json, st template using IsBuiltInType = Traits::Any, std::is_floating_point, std::is_pointer, std::is_enum, Traits::IsSpecializationOf, Traits::IsIteratable, Traits::IsSpecializationOf, - Traits::IsSpecializationOf, Traits::IsSpecializationOf>; + Traits::IsSpecializationOf, Traits::IsSpecializationOf, IsVariant>; template using IsCustomType = Traits::Not>; // define trait to check for custom structs/classes which are JSON serializable @@ -271,6 +272,32 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_ push(*reflectable, value, allocator); } +/*! + * \brief Pushes the specified variant to the specified value. + */ +template > * = nullptr> +void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) +{ + if (reflectable.valueless_by_exception()) { + value.SetNull(); + return; + } + + RAPIDJSON_NAMESPACE::Value index, data; + index.SetInt(reflectable.index()); + std::visit( + [&data, &allocator](const auto &reflectableOfActualType) { + if constexpr (!std::is_same_v, std::monostate>) { + push(reflectableOfActualType, data, allocator); + } + }, + reflectable); + + value.SetObject(); + value.AddMember("index", index, allocator); + value.AddMember("data", data, allocator); +} + /*! * \brief Pushes the specified \a reflectable which has a custom type to the specified array. */ @@ -395,6 +422,12 @@ void pull(Type &reflectable, const rapidjson::GenericValue> * = nullptr> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors); +/*! + * \brief Pulls the specified \a reflectable which is a variant from the specified value which might be null. + */ +template > * = nullptr> +void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors); + /*! * \brief Pulls the specified \a reflectable from the specified value iterator which is checked to contain the right type. */ @@ -726,6 +759,72 @@ void pull(Type &reflectable, const rapidjson::GenericValue +void assignVariantValueByRuntimeIndex(std::size_t runtimeIndex, Variant &variant, + const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) +{ + if constexpr (compiletimeIndex < std::variant_size_v) { + if (compiletimeIndex == runtimeIndex) { + if constexpr (std::is_same_v, std::monostate>) { + variant = std::monostate{}; + } else { + pull(variant.template emplace(), value, errors); + } + } else { + assignVariantValueByRuntimeIndex(runtimeIndex, variant, value, errors); + } + } else { + if (errors) { + errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantIndex, JsonType::Number, JsonType::Number, errors->currentRecord, + errors->currentMember, errors->currentIndex); + } + } +} +} // namespace Detail +/// \endcond + +/*! + * \brief Pulls the specified \a reflectable which is a variant from the specified value which might be null. + */ +template > *> +void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) +{ + if (!value.IsObject()) { + if (errors) { + errors->reportTypeMismatch(value.GetType()); + } + return; + } + + auto obj = value.GetObject(); + auto indexIterator = obj.FindMember("index"); + auto dataIterator = obj.FindMember("data"); + if (indexIterator == obj.MemberEnd() || dataIterator == obj.MemberEnd()) { + if (errors) { + errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantObject, JsonType::Object, JsonType::Object, errors->currentRecord, + errors->currentMember, errors->currentIndex); + } + return; + } + const auto &indexValue = indexIterator->value; + if (!indexValue.IsInt()) { + if (errors) { + errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantIndex, JsonType::Number, jsonType(indexValue.GetType()), + errors->currentRecord, errors->currentMember, errors->currentIndex); + } + return; + } + const auto index = indexValue.GetInt(); + if (index < 0) { + errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantIndex, JsonType::Number, JsonType::Number, errors->currentRecord, + errors->currentMember, errors->currentIndex); + return; + } + Detail::assignVariantValueByRuntimeIndex(static_cast(index), reflectable, dataIterator->value, errors); +} + /*! * \brief Pulls the specified \a reflectable from the specified value iterator which is checked to contain the right type. */ diff --git a/lib/tests/binaryreflector.cpp b/lib/tests/binaryreflector.cpp index 46a858e..8b9c8bd 100644 --- a/lib/tests/binaryreflector.cpp +++ b/lib/tests/binaryreflector.cpp @@ -65,6 +65,12 @@ struct NestingArrayBinary : public BinarySerializable { vector testObjects; }; +struct ObjectWithVariantsBinary : public BinarySerializable { + variant someVariant; + variant anotherVariant; + variant yetAnotherVariant; +}; + // pretend serialization code for structs has been generated namespace ReflectiveRapidJSON { namespace BinaryReflector { @@ -119,6 +125,20 @@ template <> void writeCustomType(BinarySerializer &serialize serializer.write(customType.testObjects); } +template <> void readCustomType(BinaryDeserializer &deserializer, ObjectWithVariantsBinary &customType) +{ + deserializer.read(customType.someVariant); + deserializer.read(customType.anotherVariant); + deserializer.read(customType.yetAnotherVariant); +} + +template <> void writeCustomType(BinarySerializer &serializer, const ObjectWithVariantsBinary &customType) +{ + serializer.write(customType.someVariant); + serializer.write(customType.anotherVariant); + serializer.write(customType.yetAnotherVariant); +} + } // namespace BinaryReflector // namespace BinaryReflector @@ -138,6 +158,7 @@ class BinaryReflectorTests : public TestFixture { CPPUNIT_TEST(testDeserializeNestedStruct); CPPUNIT_TEST(testSmallSharedPointer); CPPUNIT_TEST(testBigSharedPointer); + CPPUNIT_TEST(testVariant); CPPUNIT_TEST_SUITE_END(); public: @@ -154,6 +175,7 @@ public: void testSharedPointer(std::uintptr_t fakePointer); void testSmallSharedPointer(); void testBigSharedPointer(); + void testVariant(); private: vector m_buffer; @@ -335,3 +357,26 @@ void BinaryReflectorTests::testBigSharedPointer() { testSharedPointer(std::numeric_limits::max()); } + +void BinaryReflectorTests::testVariant() +{ + // create test object + ObjectWithVariantsBinary variants; + variants.someVariant = std::monostate{}; + variants.anotherVariant = "foo"; + variants.yetAnotherVariant = 42; + + // serialize test object + stringstream stream(ios_base::in | ios_base::out | ios_base::binary); + stream.exceptions(ios_base::failbit | ios_base::badbit); + variants.toBinary(stream); + + // deserialize the object again + const auto deserializedVariants = ObjectWithVariantsBinary::fromBinary(stream); + + CPPUNIT_ASSERT_EQUAL(2_st, deserializedVariants.someVariant.index()); + CPPUNIT_ASSERT_EQUAL(0_st, deserializedVariants.anotherVariant.index()); + CPPUNIT_ASSERT_EQUAL(1_st, deserializedVariants.yetAnotherVariant.index()); + CPPUNIT_ASSERT_EQUAL("foo"s, get<0>(deserializedVariants.anotherVariant)); + CPPUNIT_ASSERT_EQUAL(42, get<1>(deserializedVariants.yetAnotherVariant)); +} diff --git a/lib/tests/jsonreflector.cpp b/lib/tests/jsonreflector.cpp index 55f39d9..cc18459 100644 --- a/lib/tests/jsonreflector.cpp +++ b/lib/tests/jsonreflector.cpp @@ -56,6 +56,9 @@ struct TestObject : public JsonSerializable { multiset someMultiset; unordered_set someUnorderedSet; unordered_multiset someUnorderedMultiset; + variant someVariant; + variant anotherVariant; + variant yetAnotherVariant; }; struct NestingObject : public JsonSerializable { @@ -85,6 +88,9 @@ template <> inline void push(const TestObject &reflectable, Value::O push(reflectable.someMultiset, "someMultiset", value, allocator); push(reflectable.someUnorderedSet, "someUnorderedSet", value, allocator); push(reflectable.someUnorderedMultiset, "someUnorderedMultiset", value, allocator); + push(reflectable.someVariant, "someVariant", value, allocator); + push(reflectable.anotherVariant, "anotherVariant", value, allocator); + push(reflectable.yetAnotherVariant, "yetAnotherVariant", value, allocator); } template <> inline void push(const NestingObject &reflectable, Value::Object &value, Document::AllocatorType &allocator) @@ -118,6 +124,9 @@ inline void pull(TestObject &reflectable, const GenericValuecurrentRecord = previousRecord; } @@ -265,8 +274,11 @@ void JsonReflectorTests::testSerializeSimpleObjects() testObj.someMultiset = { "a", "b", "b" }; testObj.someUnorderedSet = { "a" }; testObj.someUnorderedMultiset = { "b", "b", "b" }; + testObj.someVariant = std::monostate{}; + testObj.anotherVariant = "foo"; + testObj.yetAnotherVariant = 42; CPPUNIT_ASSERT_EQUAL( - "{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"d\":false,\"c\":true},\"someSet\":[\"a\",\"b\",\"c\"],\"someMultiset\":[\"a\",\"b\",\"b\"],\"someUnorderedSet\":[\"a\"],\"someUnorderedMultiset\":[\"b\",\"b\",\"b\"]}"s, + "{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"d\":false,\"c\":true},\"someSet\":[\"a\",\"b\",\"c\"],\"someMultiset\":[\"a\",\"b\",\"b\"],\"someUnorderedSet\":[\"a\"],\"someUnorderedMultiset\":[\"b\",\"b\",\"b\"],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"foo\"},\"yetAnotherVariant\":{\"index\":1,\"data\":42}}"s, string(testObj.toJson().GetString())); } @@ -284,7 +296,7 @@ void JsonReflectorTests::testSerializeNestedObjects() testObj.text = "test"; testObj.boolean = false; CPPUNIT_ASSERT_EQUAL( - "{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}}"s, + "{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}}"s, string(nestingObj.toJson().GetString())); NestingArray nestingArray; @@ -293,13 +305,13 @@ void JsonReflectorTests::testSerializeNestedObjects() nestingArray.testObjects.emplace_back(testObj); nestingArray.testObjects.back().number = 43; CPPUNIT_ASSERT_EQUAL( - "{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]},{\"number\":43,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}]}"s, + "{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}},{\"number\":43,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]}"s, string(nestingArray.toJson().GetString())); vector nestedInVector; nestedInVector.emplace_back(testObj); CPPUNIT_ASSERT_EQUAL( - "[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}]"s, + "[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s, string(JsonReflector::toJson(nestedInVector).GetString())); } @@ -327,7 +339,7 @@ void JsonReflectorTests::testSerializeUniquePtr() Writer jsonWriter(strbuf); doc.Accept(jsonWriter); CPPUNIT_ASSERT_EQUAL( - "[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}]"s, + "[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s, string(strbuf.GetString())); } @@ -355,7 +367,7 @@ void JsonReflectorTests::testSerializeSharedPtr() Writer jsonWriter(strbuf); doc.Accept(jsonWriter); CPPUNIT_ASSERT_EQUAL( - "[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}]"s, + "[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s, string(strbuf.GetString())); } @@ -423,7 +435,8 @@ void JsonReflectorTests::testDeserializeSimpleObjects() const TestObject testObj( TestObject::fromJson("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":" "false,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"c\":true,\"d\":false},\"someSet\":[\"a\",\"b\"],\"someMultiset\":[" - "\"a\",\"a\"],\"someUnorderedSet\":[\"a\",\"b\"],\"someUnorderedMultiset\":[\"a\",\"a\"]}")); + "\"a\",\"a\"],\"someUnorderedSet\":[\"a\",\"b\"],\"someUnorderedMultiset\":[\"a\",\"a\"],\"someVariant\":{\"index\":0," + "\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"foo\"},\"yetAnotherVariant\":{\"index\":1,\"data\":42}}")); CPPUNIT_ASSERT_EQUAL(42, testObj.number); CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2); @@ -438,6 +451,11 @@ void JsonReflectorTests::testDeserializeSimpleObjects() CPPUNIT_ASSERT_EQUAL(multiset({ "a", "a" }), testObj.someMultiset); CPPUNIT_ASSERT_EQUAL(unordered_set({ "a", "b" }), testObj.someUnorderedSet); CPPUNIT_ASSERT_EQUAL(unordered_multiset({ "a", "a" }), testObj.someUnorderedMultiset); + CPPUNIT_ASSERT_EQUAL(0_st, testObj.someVariant.index()); + CPPUNIT_ASSERT_EQUAL(0_st, testObj.anotherVariant.index()); + CPPUNIT_ASSERT_EQUAL("foo"s, std::get<0>(testObj.anotherVariant)); + CPPUNIT_ASSERT_EQUAL(1_st, testObj.yetAnotherVariant.index()); + CPPUNIT_ASSERT_EQUAL(42, std::get<1>(testObj.yetAnotherVariant)); } /*! diff --git a/lib/traits.h b/lib/traits.h index 81ae6fa..5c792ca 100644 --- a/lib/traits.h +++ b/lib/traits.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace ReflectiveRapidJSON { @@ -61,6 +62,7 @@ using IsArray = Traits::All, Traits::Not>, Traits::Not>, Traits::Not>>; template using IsIteratableExceptString = Traits::All, Traits::Not>>; +template using IsVariant = Traits::All>; } // namespace ReflectiveRapidJSON