Support smart pointer

This commit is contained in:
Martchus 2017-11-12 21:09:45 +01:00
parent ce89e3d878
commit f24390a00b
4 changed files with 181 additions and 17 deletions

View File

@ -13,25 +13,27 @@ generator.
## Supported datatypes
The following table shows the mapping of supported C++ types to supported JSON types:
| C++ type | JSON type |
| ---------------------------------------- |:---------:|
| custom structures/classes | object |
| bool | true/false|
| signed and unsigned integral types | number |
| float and double | number |
| enum and enum class | number |
| std::string | string |
| const char * | string |
| iteratables (std::vector, std::list, ...)| array |
| std::tuple | array |
| C++ type | JSON type |
| -------------------------------------------- |:------------:|
| custom structures/classes | object |
| `bool` | true/false |
| signed and unsigned integral types | number |
| `float` and `double` | number |
| `enum` and `enum class` | number |
| `std::string` | string |
| `const char *` | string |
| iteratables (`std::vector`, `std::list`, ...)| array |
| `std::tuple` | array |
| `std::unique_ptr`, `std::shared_ptr` | depends/null |
### Remarks
* `const char *` is only supported for serialization.
* Raw pointer are not supported. This prevents
forgetting to free memoery which would have to be allocated when deserializing.
* For the same reason `const char *` strings are only supported for serialization.
* Enums are only supported for serialization.
* For deserialization, iteratables must provide an `emplace_back` method. So deserialization of eg. `std::forward_list`
is currently not supported.
* Using smart pointers is not supported yet.
* The JSON type `null` is not supported yet.
* The JSON type for smart pointer depends on the type the pointer refers to. It can also be `null`.
* See also TODOs.md.
## Usage

View File

@ -16,7 +16,7 @@
- [x] Allow exporting symbols
## Library-only
- [ ] Support `std::unique_ptr` and `std::shared_ptr`
- [x] Support `std::unique_ptr` and `std::shared_ptr`
- [ ] Support `std::map` and `std::unordered_map`
- [ ] Support `std::any`
- [x] Support/document customized (de)serialization (eg. serialize some `DateTime` object to ISO string representation)

View File

@ -16,6 +16,7 @@
#include <rapidjson/writer.h>
#include <limits>
#include <memory>
#include <string>
#include <tuple>
@ -67,7 +68,8 @@ inline RAPIDJSON_NAMESPACE::Document parseJsonDocFromString(const char *json, st
// define traits to distinguish between "built-in" types like int, std::string, std::vector, ... and custom structs/classes
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::tuple>, Traits::IsIteratable<Type>, Traits::IsSpecializationOf<Type, std::unique_ptr>,
Traits::IsSpecializationOf<Type, std::shared_ptr>, Traits::IsSpecializationOf<Type, std::weak_ptr>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
// define trait to check for custom structs/classes which are JSON serializable
@ -114,6 +116,17 @@ void push(
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>>...>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);
/*!
* \brief Pushes the specified \a reflectable which has a custom type to the specified value.
*/
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>>...>
inline void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
value.SetObject();
RAPIDJSON_NAMESPACE::Value::Object obj(value.GetObject());
push(reflectable, obj, allocator);
}
/*!
* \brief Pushes the specified integer/float/boolean to the specified value.
*/
@ -222,6 +235,21 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
Detail::TuplePushHelper<Type, std::tuple_size<Type>::value>::push(reflectable, array, allocator);
}
/*!
* \brief Pushes the specified unique_ptr, shared_ptr or weak_ptr to the specified value.
*/
template <typename Type,
Traits::EnableIfAny<Traits::IsSpecializationOf<Type, std::unique_ptr>, Traits::IsSpecializationOf<Type, std::shared_ptr>,
Traits::IsSpecializationOf<Type, std::weak_ptr>>...>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
if (!reflectable) {
value.SetNull();
return;
}
push(*reflectable, value, allocator);
}
/*!
* \brief Pushes the specified \a reflectable which has a custom type to the specified array.
*/
@ -409,7 +437,7 @@ template <class Tuple> struct TuplePullHelper<Tuple, 1> {
} // namespace Detail
/*!
* \brief Pulls the speciified \a reflectable which is tuple from the specified value which is checked to contain an array.
* \brief Pulls the speciified \a reflectable which is a tuple from the specified value which is checked to contain an array.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>>...>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
@ -431,6 +459,34 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
Detail::TuplePullHelper<Type, std::tuple_size<Type>::value>::pull(reflectable, array, errors);
}
/*!
* \brief Pulls the speciified \a reflectable which is a unique_ptr from the specified value which might be null.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::unique_ptr>>...>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if (value.IsNull()) {
reflectable.reset();
return;
}
reflectable = std::make_unique<typename Type::element_type>();
pull(*reflectable, value, errors);
}
/*!
* \brief Pulls the speciified \a reflectable which is a shared_ptr from the specified value which might be null.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>>...>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if (value.IsNull()) {
reflectable.reset();
return;
}
reflectable = std::make_shared<typename Type::element_type>();
pull(*reflectable, value, errors);
}
/*!
* \brief Pulls the speciified \a reflectable from the specified value iterator which is checked to contain the right type.
*/

View File

@ -150,9 +150,13 @@ class JsonReflectorTests : public TestFixture {
CPPUNIT_TEST(testSerializePrimitives);
CPPUNIT_TEST(testSerializeSimpleObjects);
CPPUNIT_TEST(testSerializeNestedObjects);
CPPUNIT_TEST(testSerializeUniquePtr);
CPPUNIT_TEST(testSerializeSharedPtr);
CPPUNIT_TEST(testDeserializePrimitives);
CPPUNIT_TEST(testDeserializeSimpleObjects);
CPPUNIT_TEST(testDeserializeNestedObjects);
CPPUNIT_TEST(testDeserializeUniquePtr);
CPPUNIT_TEST(testDeserializeSharedPtr);
CPPUNIT_TEST(testHandlingParseError);
CPPUNIT_TEST(testHandlingTypeMismatch);
CPPUNIT_TEST_SUITE_END();
@ -165,9 +169,13 @@ public:
void testSerializePrimitives();
void testSerializeSimpleObjects();
void testSerializeNestedObjects();
void testSerializeUniquePtr();
void testSerializeSharedPtr();
void testDeserializePrimitives();
void testDeserializeSimpleObjects();
void testDeserializeNestedObjects();
void testDeserializeUniquePtr();
void testDeserializeSharedPtr();
void testHandlingParseError();
void testHandlingTypeMismatch();
@ -262,6 +270,60 @@ void JsonReflectorTests::testSerializeNestedObjects()
string(nestingArray.toJson().GetString()));
}
void JsonReflectorTests::testSerializeUniquePtr()
{
Document doc(kArrayType);
Document::AllocatorType &alloc = doc.GetAllocator();
doc.SetArray();
Document::Array array(doc.GetArray());
const auto str = make_unique<string>("foo");
std::unique_ptr<string> nullStr;
const auto obj = make_unique<TestObject>();
obj->number = 42;
obj->number2 = 3.141592653589793;
obj->numbers = { 1, 2, 3, 4 };
obj->text = "bar";
obj->boolean = false;
JsonReflector::push(str, array, alloc);
JsonReflector::push(nullStr, array, alloc);
JsonReflector::push(obj, array, alloc);
StringBuffer strbuf;
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}]"s,
string(strbuf.GetString()));
}
void JsonReflectorTests::testSerializeSharedPtr()
{
Document doc(kArrayType);
Document::AllocatorType &alloc = doc.GetAllocator();
doc.SetArray();
Document::Array array(doc.GetArray());
const auto str = make_shared<string>("foo");
std::unique_ptr<string> nullStr;
const auto obj = make_shared<TestObject>();
obj->number = 42;
obj->number2 = 3.141592653589793;
obj->numbers = { 1, 2, 3, 4 };
obj->text = "bar";
obj->boolean = false;
JsonReflector::push(str, array, alloc);
JsonReflector::push(nullStr, array, alloc);
JsonReflector::push(obj, array, alloc);
StringBuffer strbuf;
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}]"s,
string(strbuf.GetString()));
}
/*!
* \brief Tests deserializing strings, numbers (int, float, double) and boolean.
*/
@ -358,6 +420,50 @@ void JsonReflectorTests::testDeserializeNestedObjects()
}
}
void JsonReflectorTests::testDeserializeUniquePtr()
{
Document doc(kArrayType);
doc.Parse("[\"foo\",null,{\"text\":\"bar\"}]");
auto array = doc.GetArray().begin();
unique_ptr<string> str;
unique_ptr<string> nullStr;
unique_ptr<TestObject> obj;
JsonDeserializationErrors errors;
JsonReflector::pull(str, array, &errors);
JsonReflector::pull(nullStr, array, &errors);
JsonReflector::pull(obj, array, &errors);
CPPUNIT_ASSERT_EQUAL(0_st, errors.size());
CPPUNIT_ASSERT(str);
CPPUNIT_ASSERT_EQUAL("foo"s, *str);
CPPUNIT_ASSERT(!nullStr);
CPPUNIT_ASSERT(obj);
CPPUNIT_ASSERT_EQUAL("bar"s, obj->text);
}
void JsonReflectorTests::testDeserializeSharedPtr()
{
Document doc(kArrayType);
doc.Parse("[\"foo\",null,{\"text\":\"bar\"}]");
auto array = doc.GetArray().begin();
shared_ptr<string> str;
shared_ptr<string> nullStr;
shared_ptr<TestObject> obj;
JsonDeserializationErrors errors;
JsonReflector::pull(str, array, &errors);
JsonReflector::pull(nullStr, array, &errors);
JsonReflector::pull(obj, array, &errors);
CPPUNIT_ASSERT_EQUAL(0_st, errors.size());
CPPUNIT_ASSERT(str);
CPPUNIT_ASSERT_EQUAL("foo"s, *str);
CPPUNIT_ASSERT(!nullStr);
CPPUNIT_ASSERT(obj);
CPPUNIT_ASSERT_EQUAL("bar"s, obj->text);
}
/*!
* \brief Tests whether RAPIDJSON_NAMESPACE::ParseResult is thrown correctly when passing invalid JSON to fromJSON().
*/