Add support for `std::optional`

This commit is contained in:
Martchus 2022-05-07 18:40:37 +02:00
parent 0633923935
commit 762540f5e5
6 changed files with 121 additions and 13 deletions

View File

@ -62,7 +62,7 @@ The following table shows the mapping of supported C++ types to supported JSON t
| iteratable lists (`std::vector`, `std::list`, ...) | array | | iteratable lists (`std::vector`, `std::list`, ...) | array |
| sets (`std::set`, `std::unordered_set`, `std::multiset`, ...) | array | | sets (`std::set`, `std::unordered_set`, `std::multiset`, ...) | array |
| `std::pair`, `std::tuple` | array | | `std::pair`, `std::tuple` | array |
| `std::unique_ptr`, `std::shared_ptr` | depends/null | | `std::unique_ptr`, `std::shared_ptr`, `std::optional` | depends/null |
| `std::map`, `std::unordered_map`, `std::multimap`, `std::unordered_multimap` | object | | `std::map`, `std::unordered_map`, `std::multimap`, `std::unordered_multimap` | object |
| `std::variant` | object | | `std::variant` | object |
| `JsonSerializable` | object | | `JsonSerializable` | object |
@ -73,7 +73,8 @@ The following table shows the mapping of supported C++ types to supported JSON t
* For the same reason `const char *` and `std::string_view` are only supported for serialization. * For the same reason `const char *` and `std::string_view` are only supported for serialization.
* Enums are (de)serialized as their underlying integer value. When deserializing, it is currently *not* checked * Enums are (de)serialized as their underlying integer value. When deserializing, it is currently *not* checked
whether the present integer value is a valid enumeration item. whether the present integer value is a valid enumeration item.
* The JSON type for smart pointers depends on the type the pointer refers to. It can also be `null`. * The JSON type for smart pointers and `std::optional` depends on the type the pointer/optional refers to.
It can also be `null` for null pointers or `std::optional` without value.
* If multiple `std::shared_ptr` instances point to the same object this object is serialized multiple times. * If multiple `std::shared_ptr` instances point to the same object this object is serialized multiple times.
When deserializing those identical objects, it is currently not possible to share the memory (again). So each When deserializing those identical objects, it is currently not possible to share the memory (again). So each
`std::shared_ptr` will point to its own copy. Note that this limitation is *not* present when using binary `std::shared_ptr` will point to its own copy. Note that this limitation is *not* present when using binary

View File

@ -29,5 +29,5 @@
- [x] Support `std::unique_ptr` and `std::shared_ptr` - [x] Support `std::unique_ptr` and `std::shared_ptr`
- [x] Support `std::map` and `std::unordered_map` - [x] Support `std::map` and `std::unordered_map`
- [ ] Support `std::any` - [ ] Support `std::any`
- [ ] Support `std::optional` - [x] Support `std::optional`
- [x] Support/document customized (de)serialization (eg. serialize some `DateTime` object to ISO string representation) - [x] Support/document customized (de)serialization (eg. serialize some `DateTime` object to ISO string representation)

View File

@ -17,6 +17,7 @@
#include <any> #include <any>
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
#include <variant> #include <variant>
@ -46,7 +47,8 @@ namespace BinaryReflector {
template <typename Type> 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, 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>, 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>, IsVariant<Type>>; Traits::IsIteratable<Type>, Traits::IsSpecializingAnyOf<Type, std::pair, std::unique_ptr, std::shared_ptr, std::optional>, std::is_enum<Type>,
IsVariant<Type>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>; template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
class BinaryDeserializer; class BinaryDeserializer;
@ -78,6 +80,7 @@ public:
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr> void read(Type &pair); template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr> void read(Type &pair);
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::unique_ptr>> * = nullptr> void read(Type &pointer); template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::unique_ptr>> * = nullptr> void read(Type &pointer);
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> * = nullptr> void read(Type &pointer); template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> * = nullptr> void read(Type &pointer);
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> * = nullptr> void read(Type &pointer);
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsResizable<Type>> * = nullptr> void read(Type &iteratable); template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsResizable<Type>> * = nullptr> void read(Type &iteratable);
template <typename Type, Traits::EnableIfAny<IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr> void read(Type &iteratable); template <typename Type, Traits::EnableIfAny<IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr> void read(Type &iteratable);
template <typename Type, template <typename Type,
@ -102,7 +105,8 @@ public:
using CppUtilities::BinaryWriter::write; using CppUtilities::BinaryWriter::write;
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr> void write(const Type &pair); template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr> void write(const Type &pair);
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::unique_ptr, std::optional>> * = 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<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<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 &enumValue); template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void write(const Type &enumValue);
@ -159,6 +163,16 @@ template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::
} }
} }
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> *> void BinaryDeserializer::read(Type &opt)
{
if (!readBool()) {
opt.reset();
return;
}
opt = std::make_optional<typename Type::value_type>();
read(*opt);
}
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsResizable<Type>> *> void BinaryDeserializer::read(Type &iteratable) template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsResizable<Type>> *> void BinaryDeserializer::read(Type &iteratable)
{ {
const auto size = readVariableLengthUIntBE(); const auto size = readVariableLengthUIntBE();
@ -247,12 +261,12 @@ template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::
write(pair.second); write(pair.second);
} }
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr>> *> void BinarySerializer::write(const Type &pointer) template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::optional>> *>
void BinarySerializer::write(const Type &opt)
{ {
const bool hasValue = pointer != nullptr; writeBool(static_cast<bool>(opt));
writeBool(hasValue); if (opt) {
if (hasValue) { write(*opt);
write(*pointer);
} }
} }

View File

@ -19,6 +19,7 @@
#include <limits> #include <limits>
#include <map> #include <map>
#include <memory> #include <memory>
#include <optional>
#include <set> #include <set>
#include <string> #include <string>
#include <tuple> #include <tuple>
@ -83,7 +84,7 @@ inline RAPIDJSON_NAMESPACE::Document parseJsonDocFromString(const char *json, st
template <typename Type> template <typename Type>
using IsBuiltInType = Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>, std::is_enum<Type>, using IsBuiltInType = Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>, std::is_enum<Type>,
Traits::IsSpecializingAnyOf<Type, std::tuple, std::pair>, Traits::IsIteratable<Type>, Traits::IsSpecializingAnyOf<Type, std::tuple, std::pair>, Traits::IsIteratable<Type>,
Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::shared_ptr, std::weak_ptr>, IsVariant<Type>>; Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional>, IsVariant<Type>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>; template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
// define trait to check for custom structs/classes which are JSON serializable // define trait to check for custom structs/classes which are JSON serializable
@ -306,10 +307,10 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
} }
/*! /*!
* \brief Pushes the specified unique_ptr, shared_ptr or weak_ptr to the specified value. * \brief Pushes the specified unique_ptr, shared_ptr, weak_ptr or optional to the specified value.
*/ */
template <typename Type, template <typename Type,
Traits::EnableIfAny<Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::shared_ptr, std::weak_ptr>> * = nullptr> Traits::EnableIfAny<Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator) void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{ {
if (!reflectable) { if (!reflectable) {
@ -478,6 +479,12 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> * = nullptr> 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); void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
/*!
* \brief Pulls the specified \a reflectable which is an std::optional from the specified value which might be null.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> * = 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. * \brief Pulls the specified \a reflectable which is a variant from the specified value which might be null.
*/ */
@ -848,6 +855,20 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
pull(*reflectable, value, errors); pull(*reflectable, value, errors);
} }
/*!
* \brief Pulls the specified \a reflectable which is an std::optional from the specified value which might be null.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if (value.IsNull()) {
reflectable.reset();
return;
}
reflectable = std::make_optional<typename Type::value_type>();
pull(*reflectable, value, errors);
}
/// \cond /// \cond
namespace Detail { namespace Detail {
template <typename Variant, std::size_t compiletimeIndex = 0> template <typename Variant, std::size_t compiletimeIndex = 0>

View File

@ -170,6 +170,7 @@ class BinaryReflectorTests : public TestFixture {
CPPUNIT_TEST(testSmallSharedPointer); CPPUNIT_TEST(testSmallSharedPointer);
CPPUNIT_TEST(testBigSharedPointer); CPPUNIT_TEST(testBigSharedPointer);
CPPUNIT_TEST(testVariant); CPPUNIT_TEST(testVariant);
CPPUNIT_TEST(testOptional);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
public: public:
@ -187,6 +188,7 @@ public:
void testSmallSharedPointer(); void testSmallSharedPointer();
void testBigSharedPointer(); void testBigSharedPointer();
void testVariant(); void testVariant();
void testOptional();
private: private:
vector<unsigned char> m_buffer; vector<unsigned char> m_buffer;
@ -391,3 +393,28 @@ void BinaryReflectorTests::testVariant()
CPPUNIT_ASSERT_EQUAL("foo"s, get<0>(deserializedVariants.anotherVariant)); CPPUNIT_ASSERT_EQUAL("foo"s, get<0>(deserializedVariants.anotherVariant));
CPPUNIT_ASSERT_EQUAL(42, get<1>(deserializedVariants.yetAnotherVariant)); CPPUNIT_ASSERT_EQUAL(42, get<1>(deserializedVariants.yetAnotherVariant));
} }
void BinaryReflectorTests::testOptional()
{
// create test objects
const auto str = std::make_optional<std::string>("foo");
const auto nullStr = std::optional<std::string>();
// serialize test object
auto stream = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
stream.exceptions(std::ios_base::failbit | std::ios_base::badbit);
auto ser = BinaryReflector::BinarySerializer(&stream);
ser.write(str);
ser.write(nullStr);
// deserialize the object again
auto deser = BinaryReflector::BinaryDeserializer(&stream);
auto deserStr = std::optional<std::string>();
auto deserNullStr = std::optional<std::string>();
deser.read(deserStr);
deser.read(deserNullStr);
CPPUNIT_ASSERT(deserStr.has_value());
CPPUNIT_ASSERT_EQUAL("foo"s, deserStr.value());
CPPUNIT_ASSERT(!nullStr.has_value());
}

View File

@ -186,11 +186,13 @@ class JsonReflectorTests : public TestFixture {
CPPUNIT_TEST(testSerializeNestedObjects); CPPUNIT_TEST(testSerializeNestedObjects);
CPPUNIT_TEST(testSerializeUniquePtr); CPPUNIT_TEST(testSerializeUniquePtr);
CPPUNIT_TEST(testSerializeSharedPtr); CPPUNIT_TEST(testSerializeSharedPtr);
CPPUNIT_TEST(testSerializeOptional);
CPPUNIT_TEST(testDeserializePrimitives); CPPUNIT_TEST(testDeserializePrimitives);
CPPUNIT_TEST(testDeserializeSimpleObjects); CPPUNIT_TEST(testDeserializeSimpleObjects);
CPPUNIT_TEST(testDeserializeNestedObjects); CPPUNIT_TEST(testDeserializeNestedObjects);
CPPUNIT_TEST(testDeserializeUniquePtr); CPPUNIT_TEST(testDeserializeUniquePtr);
CPPUNIT_TEST(testDeserializeSharedPtr); CPPUNIT_TEST(testDeserializeSharedPtr);
CPPUNIT_TEST(testDeserializeOptional);
CPPUNIT_TEST(testHandlingParseError); CPPUNIT_TEST(testHandlingParseError);
CPPUNIT_TEST(testHandlingTypeMismatch); CPPUNIT_TEST(testHandlingTypeMismatch);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
@ -205,11 +207,13 @@ public:
void testSerializeNestedObjects(); void testSerializeNestedObjects();
void testSerializeUniquePtr(); void testSerializeUniquePtr();
void testSerializeSharedPtr(); void testSerializeSharedPtr();
void testSerializeOptional();
void testDeserializePrimitives(); void testDeserializePrimitives();
void testDeserializeSimpleObjects(); void testDeserializeSimpleObjects();
void testDeserializeNestedObjects(); void testDeserializeNestedObjects();
void testDeserializeUniquePtr(); void testDeserializeUniquePtr();
void testDeserializeSharedPtr(); void testDeserializeSharedPtr();
void testDeserializeOptional();
void testHandlingParseError(); void testHandlingParseError();
void testHandlingTypeMismatch(); void testHandlingTypeMismatch();
@ -379,6 +383,28 @@ void JsonReflectorTests::testSerializeSharedPtr()
string(strbuf.GetString())); string(strbuf.GetString()));
} }
/*!
* \brief Tests serializing std::optional.
*/
void JsonReflectorTests::testSerializeOptional()
{
Document doc(kArrayType);
Document::AllocatorType &alloc = doc.GetAllocator();
doc.SetArray();
Document::Array array(doc.GetArray());
const auto str = make_optional<std::string>("foo");
const auto nullStr = std::optional<std::string>();
JsonReflector::push(str, array, alloc);
JsonReflector::push(nullStr, array, alloc);
StringBuffer strbuf;
Writer<StringBuffer> jsonWriter(strbuf);
doc.Accept(jsonWriter);
CPPUNIT_ASSERT_EQUAL("[\"foo\",null]"s, std::string(strbuf.GetString()));
}
/*! /*!
* \brief Tests deserializing strings, numbers (int, float, double) and boolean. * \brief Tests deserializing strings, numbers (int, float, double) and boolean.
*/ */
@ -517,6 +543,9 @@ void JsonReflectorTests::testDeserializeNestedObjects()
CPPUNIT_ASSERT_EQUAL("test"s, nestedInVector[0].text); CPPUNIT_ASSERT_EQUAL("test"s, nestedInVector[0].text);
} }
/*!
* \brief Tests deserializing std::optional.
*/
void JsonReflectorTests::testDeserializeUniquePtr() void JsonReflectorTests::testDeserializeUniquePtr()
{ {
Document doc(kArrayType); Document doc(kArrayType);
@ -561,6 +590,22 @@ void JsonReflectorTests::testDeserializeSharedPtr()
CPPUNIT_ASSERT_EQUAL("bar"s, obj->text); CPPUNIT_ASSERT_EQUAL("bar"s, obj->text);
} }
void JsonReflectorTests::testDeserializeOptional()
{
Document doc(kArrayType);
doc.Parse("[\"foo\",null]");
auto array = doc.GetArray().begin();
optional<string> str = "foo"s;
optional<string> nullStr;
JsonDeserializationErrors errors;
JsonReflector::pull(str, array, &errors);
CPPUNIT_ASSERT_EQUAL(0_st, errors.size());
CPPUNIT_ASSERT(str.has_value());
CPPUNIT_ASSERT_EQUAL("foo"s, *str);
CPPUNIT_ASSERT(!nullStr.has_value());
}
/*! /*!
* \brief Tests whether RAPIDJSON_NAMESPACE::ParseResult is thrown correctly when passing invalid JSON to fromJSON(). * \brief Tests whether RAPIDJSON_NAMESPACE::ParseResult is thrown correctly when passing invalid JSON to fromJSON().
*/ */