Add support for `std::optional`
This commit is contained in:
parent
0633923935
commit
762540f5e5
|
@ -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 |
|
||||
| sets (`std::set`, `std::unordered_set`, `std::multiset`, ...) | 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::variant` | 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.
|
||||
* 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.
|
||||
* 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.
|
||||
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
|
||||
|
|
2
TODOs.md
2
TODOs.md
|
@ -29,5 +29,5 @@
|
|||
- [x] Support `std::unique_ptr` and `std::shared_ptr`
|
||||
- [x] Support `std::map` and `std::unordered_map`
|
||||
- [ ] 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)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <any>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
|
@ -46,7 +47,8 @@ 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>, 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>>;
|
||||
|
||||
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::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::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::EnableIfAny<IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr> void read(Type &iteratable);
|
||||
template <typename Type,
|
||||
|
@ -102,7 +105,8 @@ public:
|
|||
|
||||
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::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<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);
|
||||
|
@ -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)
|
||||
{
|
||||
const auto size = readVariableLengthUIntBE();
|
||||
|
@ -247,12 +261,12 @@ template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::
|
|||
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(hasValue);
|
||||
if (hasValue) {
|
||||
write(*pointer);
|
||||
writeBool(static_cast<bool>(opt));
|
||||
if (opt) {
|
||||
write(*opt);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
@ -83,7 +84,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::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>>;
|
||||
|
||||
// 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,
|
||||
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)
|
||||
{
|
||||
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>
|
||||
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.
|
||||
*/
|
||||
|
@ -848,6 +855,20 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
|
|||
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
|
||||
namespace Detail {
|
||||
template <typename Variant, std::size_t compiletimeIndex = 0>
|
||||
|
|
|
@ -170,6 +170,7 @@ class BinaryReflectorTests : public TestFixture {
|
|||
CPPUNIT_TEST(testSmallSharedPointer);
|
||||
CPPUNIT_TEST(testBigSharedPointer);
|
||||
CPPUNIT_TEST(testVariant);
|
||||
CPPUNIT_TEST(testOptional);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
|
@ -187,6 +188,7 @@ public:
|
|||
void testSmallSharedPointer();
|
||||
void testBigSharedPointer();
|
||||
void testVariant();
|
||||
void testOptional();
|
||||
|
||||
private:
|
||||
vector<unsigned char> m_buffer;
|
||||
|
@ -391,3 +393,28 @@ void BinaryReflectorTests::testVariant()
|
|||
CPPUNIT_ASSERT_EQUAL("foo"s, get<0>(deserializedVariants.anotherVariant));
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -186,11 +186,13 @@ class JsonReflectorTests : public TestFixture {
|
|||
CPPUNIT_TEST(testSerializeNestedObjects);
|
||||
CPPUNIT_TEST(testSerializeUniquePtr);
|
||||
CPPUNIT_TEST(testSerializeSharedPtr);
|
||||
CPPUNIT_TEST(testSerializeOptional);
|
||||
CPPUNIT_TEST(testDeserializePrimitives);
|
||||
CPPUNIT_TEST(testDeserializeSimpleObjects);
|
||||
CPPUNIT_TEST(testDeserializeNestedObjects);
|
||||
CPPUNIT_TEST(testDeserializeUniquePtr);
|
||||
CPPUNIT_TEST(testDeserializeSharedPtr);
|
||||
CPPUNIT_TEST(testDeserializeOptional);
|
||||
CPPUNIT_TEST(testHandlingParseError);
|
||||
CPPUNIT_TEST(testHandlingTypeMismatch);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
@ -205,11 +207,13 @@ public:
|
|||
void testSerializeNestedObjects();
|
||||
void testSerializeUniquePtr();
|
||||
void testSerializeSharedPtr();
|
||||
void testSerializeOptional();
|
||||
void testDeserializePrimitives();
|
||||
void testDeserializeSimpleObjects();
|
||||
void testDeserializeNestedObjects();
|
||||
void testDeserializeUniquePtr();
|
||||
void testDeserializeSharedPtr();
|
||||
void testDeserializeOptional();
|
||||
void testHandlingParseError();
|
||||
void testHandlingTypeMismatch();
|
||||
|
||||
|
@ -379,6 +383,28 @@ void JsonReflectorTests::testSerializeSharedPtr()
|
|||
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.
|
||||
*/
|
||||
|
@ -517,6 +543,9 @@ void JsonReflectorTests::testDeserializeNestedObjects()
|
|||
CPPUNIT_ASSERT_EQUAL("test"s, nestedInVector[0].text);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests deserializing std::optional.
|
||||
*/
|
||||
void JsonReflectorTests::testDeserializeUniquePtr()
|
||||
{
|
||||
Document doc(kArrayType);
|
||||
|
@ -561,6 +590,22 @@ void JsonReflectorTests::testDeserializeSharedPtr()
|
|||
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().
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue