diff --git a/README.md b/README.md index 5255923..8963310 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/TODOs.md b/TODOs.md index 46fe563..01f647b 100644 --- a/TODOs.md +++ b/TODOs.md @@ -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) diff --git a/lib/json/reflector.h b/lib/json/reflector.h index b5658c8..b5888d6 100644 --- a/lib/json/reflector.h +++ b/lib/json/reflector.h @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -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 using IsBuiltInType = Traits::Any, std::is_floating_point, std::is_pointer, std::is_enum, - Traits::IsSpecializationOf, Traits::IsIteratable>; + Traits::IsSpecializationOf, Traits::IsIteratable, Traits::IsSpecializationOf, + Traits::IsSpecializationOf, Traits::IsSpecializationOf>; template using IsCustomType = Traits::Not>; // define trait to check for custom structs/classes which are JSON serializable @@ -114,6 +116,17 @@ void push( template >...> 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 >...> +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::value>::push(reflectable, array, allocator); } +/*! + * \brief Pushes the specified unique_ptr, shared_ptr or weak_ptr to the specified value. + */ +template , Traits::IsSpecializationOf, + Traits::IsSpecializationOf>...> +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 struct TuplePullHelper { } // 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 >...> void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) @@ -431,6 +459,34 @@ void pull(Type &reflectable, const rapidjson::GenericValue::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 >...> +void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) +{ + if (value.IsNull()) { + reflectable.reset(); + return; + } + reflectable = std::make_unique(); + pull(*reflectable, value, errors); +} + +/*! + * \brief Pulls the speciified \a reflectable which is a shared_ptr from the specified value which might be null. + */ +template >...> +void pull(Type &reflectable, const rapidjson::GenericValue> &value, JsonDeserializationErrors *errors) +{ + if (value.IsNull()) { + reflectable.reset(); + return; + } + reflectable = std::make_shared(); + pull(*reflectable, value, errors); +} + /*! * \brief Pulls the speciified \a reflectable from the specified value iterator which is checked to contain the right type. */ diff --git a/lib/tests/jsonreflector.cpp b/lib/tests/jsonreflector.cpp index fe513a4..2ffe3d8 100644 --- a/lib/tests/jsonreflector.cpp +++ b/lib/tests/jsonreflector.cpp @@ -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("foo"); + std::unique_ptr nullStr; + const auto obj = make_unique(); + 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 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("foo"); + std::unique_ptr nullStr; + const auto obj = make_shared(); + 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 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 str; + unique_ptr nullStr; + unique_ptr 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 str; + shared_ptr nullStr; + shared_ptr 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(). */