Support `std::variant`

This commit is contained in:
Martchus 2019-11-03 22:02:56 +01:00
parent dd652b4de7
commit c3dc381425
8 changed files with 231 additions and 12 deletions

View File

@ -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)

View File

@ -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.

View File

@ -18,6 +18,7 @@
#include <memory>
#include <string>
#include <tuple>
#include <variant>
namespace ReflectiveRapidJSON {
@ -40,7 +41,7 @@ namespace BinaryReflector {
template <typename Type>
using IsBuiltInType = Traits::Any<Traits::IsAnyOf<Type, char, std::uint8_t, bool, std::string, std::int16_t, std::uint16_t, std::int32_t,
std::uint32_t, std::int64_t, std::uint64_t, float, double>,
Traits::IsIteratable<Type>, Traits::IsSpecializingAnyOf<Type, std::pair, std::unique_ptr, std::shared_ptr>, std::is_enum<Type>>;
Traits::IsIteratable<Type>, Traits::IsSpecializingAnyOf<Type, std::pair, std::unique_ptr, std::shared_ptr>, std::is_enum<Type>, IsVariant<Type>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
class BinaryDeserializer;
@ -63,7 +64,8 @@ public:
Traits::EnableIf<IsIteratableExceptString<Type>,
Traits::None<IsMapOrHash<Type>, IsMultiMapOrHash<Type>, Traits::All<IsArray<Type>, Traits::IsResizable<Type>>>> * = nullptr>
void read(Type &iteratable);
template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void read(Type &customType);
template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void read(Type &enumValue);
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr> void read(Type &variant);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void read(Type &customType);
std::unordered_map<std::uint64_t, std::any> m_pointer;
@ -78,7 +80,8 @@ public:
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr>> * = nullptr> void write(const Type &pointer);
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::shared_ptr>> * = nullptr> void write(const Type &pointer);
template <typename Type, Traits::EnableIf<IsIteratableExceptString<Type>, Traits::HasSize<Type>> * = nullptr> void write(const Type &iteratable);
template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void write(const Type &customType);
template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void write(const Type &enumValue);
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr> void write(const Type &variant);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void write(const Type &customType);
std::unordered_map<std::uint64_t, bool> m_pointer;
@ -168,6 +171,33 @@ template <typename Type, Traits::EnableIf<std::is_enum<Type>> *> void BinaryDese
enumValue = static_cast<Type>(value);
}
/// \cond
namespace Detail {
template <typename Variant, std::size_t compiletimeIndex = 0>
void readVariantValueByRuntimeIndex(std::size_t runtimeIndex, Variant &variant, BinaryDeserializer &deserializer)
{
if constexpr (compiletimeIndex < std::variant_size_v<Variant>) {
if (compiletimeIndex == runtimeIndex) {
if constexpr (std::is_same_v<std::variant_alternative_t<compiletimeIndex, Variant>, std::monostate>) {
variant = std::monostate{};
} else {
deserializer.read(variant.template emplace<compiletimeIndex>());
}
} else {
readVariantValueByRuntimeIndex<Variant, compiletimeIndex + 1>(runtimeIndex, variant, deserializer);
}
} else {
throw CppUtilities::ConversionException("Variant index is out of expected range");
}
}
} // namespace Detail
/// \endcond
template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinaryDeserializer::read(Type &variant)
{
Detail::readVariantValueByRuntimeIndex(readByte(), variant, *this);
}
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinaryDeserializer::read(Type &customType)
{
readCustomType(*this, customType);
@ -232,6 +262,19 @@ template <typename Type, Traits::EnableIf<std::is_enum<Type>> *> void BinarySeri
write(static_cast<typename std::underlying_type<Type>::type>(enumValue));
}
template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinarySerializer::write(const Type &variant)
{
static_assert(std::variant_size_v<Type> < std::numeric_limits<std::uint8_t>::max(), "index will not exceed limit");
writeByte(static_cast<std::uint8_t>(variant.index()));
std::visit(
[this](const auto &valueOfActualType) {
if constexpr (!std::is_same_v<std::decay_t<decltype(valueOfActualType)>, std::monostate>) {
write(valueOfActualType);
}
},
variant);
}
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinarySerializer::write(const Type &customType)
{
writeCustomType(*this, customType);

View File

@ -14,6 +14,7 @@
#include <limits>
#include <list>
#include <string>
#include <variant>
#include <vector>
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 <typename Type, Traits::EnableIf<std::is_same<Type, std::monostate>> * = nullptr> constexpr JsonType jsonType()
{
return JsonType::Null;
}
template <typename Type, Traits::EnableIfAny<std::is_same<Type, bool>> * = nullptr> constexpr JsonType jsonType()
{
return JsonType::Bool;

View File

@ -18,6 +18,7 @@
#include <memory>
#include <string>
#include <tuple>
#include <variant>
#include "./errorhandling.h"
@ -75,7 +76,7 @@ inline RAPIDJSON_NAMESPACE::Document parseJsonDocFromString(const char *json, st
template <typename Type>
using IsBuiltInType = Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>, std::is_enum<Type>,
Traits::IsSpecializationOf<Type, std::tuple>, Traits::IsIteratable<Type>, Traits::IsSpecializationOf<Type, std::unique_ptr>,
Traits::IsSpecializationOf<Type, std::shared_ptr>, Traits::IsSpecializationOf<Type, std::weak_ptr>>;
Traits::IsSpecializationOf<Type, std::shared_ptr>, Traits::IsSpecializationOf<Type, std::weak_ptr>, IsVariant<Type>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
// 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 <typename Type, Traits::EnableIf<IsVariant<Type>> * = 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::decay_t<decltype(reflectableOfActualType)>, 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<RAPIDJSON_NAMESPACE::
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
/*!
* \brief Pulls the specified \a reflectable which is a variant from the specified value which might be null.
*/
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &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<RAPIDJSON_NAMESPACE::
pull(*reflectable, value, errors);
}
/// \cond
namespace Detail {
template <typename Variant, std::size_t compiletimeIndex = 0>
void assignVariantValueByRuntimeIndex(std::size_t runtimeIndex, Variant &variant,
const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if constexpr (compiletimeIndex < std::variant_size_v<Variant>) {
if (compiletimeIndex == runtimeIndex) {
if constexpr (std::is_same_v<std::variant_alternative_t<compiletimeIndex, Variant>, std::monostate>) {
variant = std::monostate{};
} else {
pull(variant.template emplace<compiletimeIndex>(), value, errors);
}
} else {
assignVariantValueByRuntimeIndex<Variant, compiletimeIndex + 1>(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 <typename Type, Traits::EnableIf<IsVariant<Type>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if (!value.IsObject()) {
if (errors) {
errors->reportTypeMismatch<Type>(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<std::size_t>(index), reflectable, dataIterator->value, errors);
}
/*!
* \brief Pulls the specified \a reflectable from the specified value iterator which is checked to contain the right type.
*/

View File

@ -65,6 +65,12 @@ struct NestingArrayBinary : public BinarySerializable<NestingArrayBinary> {
vector<TestObjectBinary> testObjects;
};
struct ObjectWithVariantsBinary : public BinarySerializable<ObjectWithVariantsBinary> {
variant<int, string, monostate> someVariant;
variant<string, float> anotherVariant;
variant<string, int> yetAnotherVariant;
};
// pretend serialization code for structs has been generated
namespace ReflectiveRapidJSON {
namespace BinaryReflector {
@ -119,6 +125,20 @@ template <> void writeCustomType<NestingArrayBinary>(BinarySerializer &serialize
serializer.write(customType.testObjects);
}
template <> void readCustomType<ObjectWithVariantsBinary>(BinaryDeserializer &deserializer, ObjectWithVariantsBinary &customType)
{
deserializer.read(customType.someVariant);
deserializer.read(customType.anotherVariant);
deserializer.read(customType.yetAnotherVariant);
}
template <> void writeCustomType<ObjectWithVariantsBinary>(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<unsigned char> m_buffer;
@ -335,3 +357,26 @@ void BinaryReflectorTests::testBigSharedPointer()
{
testSharedPointer(std::numeric_limits<std::uintptr_t>::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));
}

View File

@ -56,6 +56,9 @@ struct TestObject : public JsonSerializable<TestObject> {
multiset<string> someMultiset;
unordered_set<string> someUnorderedSet;
unordered_multiset<string> someUnorderedMultiset;
variant<monostate, string, int, float> someVariant;
variant<string, int, float> anotherVariant;
variant<string, int, float> yetAnotherVariant;
};
struct NestingObject : public JsonSerializable<NestingObject> {
@ -85,6 +88,9 @@ template <> inline void push<TestObject>(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<NestingObject>(const NestingObject &reflectable, Value::Object &value, Document::AllocatorType &allocator)
@ -118,6 +124,9 @@ inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<ch
pull(reflectable.someMultiset, "someMultiset", value, errors);
pull(reflectable.someUnorderedSet, "someUnorderedSet", value, errors);
pull(reflectable.someUnorderedMultiset, "someUnorderedMultiset", value, errors);
pull(reflectable.someVariant, "someVariant", value, errors);
pull(reflectable.anotherVariant, "anotherVariant", value, errors);
pull(reflectable.yetAnotherVariant, "yetAnotherVariant", value, errors);
if (errors) {
errors->currentRecord = 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<TestObject> 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<StringBuffer> 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<StringBuffer> 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<string>({ "a", "a" }), testObj.someMultiset);
CPPUNIT_ASSERT_EQUAL(unordered_set<string>({ "a", "b" }), testObj.someUnorderedSet);
CPPUNIT_ASSERT_EQUAL(unordered_multiset<string>({ "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));
}
/*!

View File

@ -8,6 +8,7 @@
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <variant>
namespace ReflectiveRapidJSON {
@ -61,6 +62,7 @@ using IsArray = Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSp
Traits::Not<IsMapOrHash<Type>>, Traits::Not<IsSet<Type>>, Traits::Not<IsMultiSet<Type>>>;
template <typename Type>
using IsIteratableExceptString = Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>;
template <typename Type> using IsVariant = Traits::All<Traits::IsSpecializationOf<Type, std::variant>>;
} // namespace ReflectiveRapidJSON