From 316c1ba83832bcb98f03c35bade48a629da5b51f Mon Sep 17 00:00:00 2001 From: Martchus Date: Sat, 23 Jun 2018 14:35:43 +0200 Subject: [PATCH] Add library for basic binary (de)serialization --- CMakeLists.txt | 2 +- lib/CMakeLists.txt | 23 +- lib/binary/reflector-boosthana.h | 30 +++ lib/binary/reflector-chronoutilities.h | 42 ++++ lib/binary/reflector.h | 168 ++++++++++++++ lib/binary/serializable.h | 50 ++++ lib/tests/binaryreflector-boosthana.cpp | 70 ++++++ lib/tests/binaryreflector.cpp | 296 ++++++++++++++++++++++++ lib/tests/jsonreflector.cpp | 41 ++-- lib/tests/traits.cpp | 21 ++ lib/traits.h | 31 +++ 11 files changed, 744 insertions(+), 30 deletions(-) create mode 100644 lib/binary/reflector-boosthana.h create mode 100644 lib/binary/reflector-chronoutilities.h create mode 100644 lib/binary/reflector.h create mode 100644 lib/binary/serializable.h create mode 100644 lib/tests/binaryreflector-boosthana.cpp create mode 100644 lib/tests/binaryreflector.cpp create mode 100644 lib/tests/traits.cpp create mode 100644 lib/traits.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9385707..388ed9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,7 @@ else() endif() # find c++utilities -find_package(c++utilities 4.12.0 REQUIRED) +find_package(c++utilities 4.15.0 REQUIRED) # use the source directory of c++utilities for includes rather than the location where headers are going to be installed # note: this enables the tests to find the header files for c++utilities in case it is built within the same project diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 2cddd3b..7b28316 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -6,6 +6,7 @@ set(META_HEADER_ONLY_LIB ON) # add project files set(HEADER_FILES + traits.h ) set(SRC_FILES ) @@ -37,10 +38,28 @@ if(RapidJSON_FOUND) ) endif() +# add binary (de)serialization specific sources +list(APPEND HEADER_FILES + binary/reflector.h + binary/reflector-boosthana.h + binary/reflector-chronoutilities.h + binary/serializable.h +) +list(APPEND TEST_SRC_FILES + tests/traits.cpp + tests/binaryreflector.cpp + tests/binaryreflector-boosthana.cpp +) + # add (only) the CMake module and include dirs for c++utilities because we're not depending on the actual library list(APPEND CMAKE_MODULE_PATH ${CPP_UTILITIES_MODULE_DIRS}) -list(APPEND PUBLIC_SHARED_INCLUDE_DIRS "${CPP_UTILITIES_INCLUDE_DIRS}") -list(APPEND PUBLIC_STATIC_INCLUDE_DIRS "${CPP_UTILITIES_INCLUDE_DIRS}") +if (CPP_UTILITIES_SOURCE_DIR) + list(APPEND PUBLIC_SHARED_INCLUDE_DIRS "${CPP_UTILITIES_SOURCE_DIR}/..") + list(APPEND PUBLIC_STATIC_INCLUDE_DIRS "${CPP_UTILITIES_SOURCE_DIR}/..") +else() + list(APPEND PUBLIC_SHARED_INCLUDE_DIRS "${CPP_UTILITIES_INCLUDE_DIRS}") + list(APPEND PUBLIC_STATIC_INCLUDE_DIRS "${CPP_UTILITIES_INCLUDE_DIRS}") +endif() # find RapidJSON, also add only the include dirs because RapidJSON is a header-only library if(RapidJSON_FOUND) diff --git a/lib/binary/reflector-boosthana.h b/lib/binary/reflector-boosthana.h new file mode 100644 index 0000000..611a480 --- /dev/null +++ b/lib/binary/reflector-boosthana.h @@ -0,0 +1,30 @@ +#ifndef REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_BOOST_HANA_H +#define REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_BOOST_HANA_H + +/*! + * \file reflector-boosthana.h + * \brief Contains generic functions relying on Boost.Hana which can replace the code which would + * otherwise had to be generated. + * \remarks + * These functions use boost::hana::keys() and boost::hana::at_key() rather than the "plain" + * for-loop shown in the introspection examples of the Boost.Hana documentation. The reason is that + * the "plain" for-loop involves making copies. This costs performance and - more importantly - prevents + * modifying the actual object. + */ + +#include "./reflector.h" + +#include +#include +#include +#include +#include +#include + +namespace BinaryReflector { +namespace JsonReflector { + +} // namespace JsonReflector +} // namespace BinaryReflector + +#endif // REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_BOOST_HANA_H diff --git a/lib/binary/reflector-chronoutilities.h b/lib/binary/reflector-chronoutilities.h new file mode 100644 index 0000000..d211b01 --- /dev/null +++ b/lib/binary/reflector-chronoutilities.h @@ -0,0 +1,42 @@ +#ifndef REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_CHRONO_UTILITIES_H +#define REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_CHRONO_UTILITIES_H + +/*! + * \file reflector-chronoutilities.h + * \brief Contains functions for (de)serializing objects from the chrono utilities provided by the + * C++ utilities library. + * \remarks This file demonstrates implementing custom (de)serialization for specific types. + */ + +#include "./reflector.h" + +#include +#include + +namespace ReflectiveRapidJSON { +namespace BinaryReflector { + +template <> void readCustomType(BinaryDeserializer &deserializer, ChronoUtilities::DateTime &dateTime) +{ + deserializer.read(dateTime.ticks()); +} + +template <> void writeCustomType(BinarySerializer &serializer, const ChronoUtilities::DateTime &dateTime) +{ + serializer.write(dateTime.totalTicks()); +} + +template <> void readCustomType(BinaryDeserializer &deserializer, ChronoUtilities::TimeSpan &timeSpan) +{ + deserializer.read(timeSpan.ticks()); +} + +template <> void writeCustomType(BinarySerializer &serializer, const ChronoUtilities::TimeSpan &timeSpan) +{ + serializer.write(timeSpan.totalTicks()); +} + +} // namespace BinaryReflector +} // namespace ReflectiveRapidJSON + +#endif // REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_CHRONO_UTILITIES_H diff --git a/lib/binary/reflector.h b/lib/binary/reflector.h new file mode 100644 index 0000000..af0d85f --- /dev/null +++ b/lib/binary/reflector.h @@ -0,0 +1,168 @@ +#ifndef REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_H +#define REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_H + +/*! + * \file reflector.h + * \brief Contains BinaryReader and BinaryWriter supporting binary (de)serialization + * of primitive and custom types. + */ + +#include "../traits.h" + +#include +#include +#include + +#include +#include +#include +#include + +namespace ReflectiveRapidJSON { + +/*! + * \brief The AdaptedBinarySerializable class allows considering 3rd party classes as serializable. + */ +template struct AdaptedBinarySerializable : public Traits::Bool { + static constexpr const char *name = "AdaptedBinarySerializable"; + static constexpr const char *qualifiedName = "ReflectiveRapidJSON::AdaptedBinarySerializable"; +}; + +template struct BinarySerializable; + +/*! + * \brief The BinaryReflector namespace contains BinaryReader and BinaryWriter for automatic binary (de)serialization. + */ +namespace BinaryReflector { + +// define traits to distinguish between "built-in" types like int, std::string, std::vector, ... and custom structs/classes +template +using IsBuiltInType = Traits::Any, + Traits::IsIteratable, Traits::IsSpecializationOf, std::is_enum>; +template using IsCustomType = Traits::Not>; +template +using IsSerializable = Traits::All< + Traits::Any>, std::is_base_of, Type>, AdaptedBinarySerializable>, + Traits::Not>>; + +class BinaryDeserializer; +class BinarySerializer; + +template > * = nullptr> void readCustomType(BinaryDeserializer &deserializer, Type &customType); +template > * = nullptr> void writeCustomType(BinarySerializer &serializer, const Type &customType); + +class BinaryDeserializer : public IoUtilities::BinaryReader { +public: + BinaryDeserializer(std::istream *stream); + + using IoUtilities::BinaryReader::read; + template > * = nullptr> void read(Type &pair); + template , Traits::IsResizable> * = nullptr> void read(Type &iteratable); + template > * = nullptr> void read(Type &iteratable); + template , Traits::None, Traits::All, Traits::IsResizable>>> + * = nullptr> + void read(Type &iteratable); + template > * = nullptr> void read(Type &customType); + template > * = nullptr> void read(Type &customType); +}; + +class BinarySerializer : public IoUtilities::BinaryWriter { +public: + BinarySerializer(std::ostream *stream); + + using IoUtilities::BinaryWriter::write; + template > * = nullptr> void write(const Type &pair); + template , Traits::HasSize> * = nullptr> void write(const Type &iteratable); + template > * = nullptr> void write(const Type &customType); + template > * = nullptr> void write(const Type &customType); +}; + +inline BinaryDeserializer::BinaryDeserializer(std::istream *stream) + : IoUtilities::BinaryReader(stream) +{ +} + +template > *> void BinaryDeserializer::read(Type &pair) +{ + read(pair.first); + read(pair.second); +} + +template , Traits::IsResizable> *> void BinaryDeserializer::read(Type &iteratable) +{ + const auto size = readVariableLengthUIntBE(); + iteratable.resize(size); + for (auto &element : iteratable) { + read(element); + } +} + +template > *> void BinaryDeserializer::read(Type &iteratable) +{ + const auto size = readVariableLengthUIntBE(); + for (size_t i = 0; i != size; ++i) { + std::pair::type, typename Type::value_type::second_type> value; + read(value); + iteratable.emplace(std::move(value)); + } +} + +template , Traits::None, Traits::All, Traits::IsResizable>>> *> +void BinaryDeserializer::read(Type &iteratable) +{ + const auto size = readVariableLengthUIntBE(); + for (size_t i = 0; i != size; ++i) { + typename Type::value_type value; + read(value); + iteratable.emplace(std::move(value)); + } +} + +template > *> void BinaryDeserializer::read(Type &enumValue) +{ + typename std::underlying_type::type value; + read(value); + enumValue = static_cast(value); +} + +template > *> void BinaryDeserializer::read(Type &customType) +{ + readCustomType(*this, customType); +} + +inline BinarySerializer::BinarySerializer(std::ostream *stream) + : IoUtilities::BinaryWriter(stream) +{ +} + +template > *> void BinarySerializer::write(const Type &pair) +{ + write(pair.first); + write(pair.second); +} + +template , Traits::HasSize> *> +void BinarySerializer::write(const Type &iteratable) +{ + writeVariableLengthUIntBE(iteratable.size()); + for (const auto &element : iteratable) { + write(element); + } +} + +template > *> void BinarySerializer::write(const Type &enumValue) +{ + write(static_cast::type>(enumValue)); +} + +template > *> void BinarySerializer::write(const Type &customType) +{ + writeCustomType(*this, customType); +} + +} // namespace BinaryReflector +} // namespace ReflectiveRapidJSON + +#endif // REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_H diff --git a/lib/binary/serializable.h b/lib/binary/serializable.h new file mode 100644 index 0000000..f514151 --- /dev/null +++ b/lib/binary/serializable.h @@ -0,0 +1,50 @@ +#ifndef REFLECTIVE_RAPIDJSON_BINARY_SERIALIZABLE_H +#define REFLECTIVE_RAPIDJSON_BINARY_SERIALIZABLE_H + +/*! + * \file serializable.h + * \brief Contains only the definiation of the BinarySerializable template class which makes the reflection + * accessible. The actual implementation is found in binaryreflector.h and generated files. + */ + +#include "./reflector.h" + +#include +#include + +namespace ReflectiveRapidJSON { + +/*! + * \brief The BinarySerializable class provides the CRTP-base for (de)serializable objects. + */ +template struct BinarySerializable { + void serialize(std::ostream &outputStream) const; + void deserialize(std::istream &inputStream); + + static constexpr const char *qualifiedName = "ReflectiveRapidJSON::BinarySerializable"; +}; + +template inline void BinarySerializable::serialize(std::ostream &outputStream) const +{ + BinaryReflector::BinarySerializer(&outputStream).write(static_cast(*this)); +} + +template inline void BinarySerializable::deserialize(std::istream &inputStream) +{ + BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast(*this)); +} + +/*! + * \def The REFLECTIVE_RAPIDJSON_MAKE_BINARY_SERIALIZABLE macro allows to adapt (de)serialization for types defined in 3rd party header files. + * \remarks The struct will not have the toBinary() and fromBinary() methods available. Use the corresponding functions in the namespace + * ReflectiveRapidJSON::BinaryReflector instead. + * \todo GCC complains when putting :: before "ReflectiveRapidJSON" namespace: "global qualification of class name is invalid before ':' token" + * Find out whether this is a compiler bug or a correct error message. + */ +#define REFLECTIVE_RAPIDJSON_MAKE_BINARY_SERIALIZABLE(T) \ + template <> struct ReflectiveRapidJSON::AdaptedBinarySerializable : Traits::Bool { \ + } + +} // namespace ReflectiveRapidJSON + +#endif // REFLECTIVE_RAPIDJSON_BINARY_SERIALIZABLE_H diff --git a/lib/tests/binaryreflector-boosthana.cpp b/lib/tests/binaryreflector-boosthana.cpp new file mode 100644 index 0000000..7e071ce --- /dev/null +++ b/lib/tests/binaryreflector-boosthana.cpp @@ -0,0 +1,70 @@ +#include "../binary/reflector-boosthana.h" +#include "../binary/serializable.h" + +#include +#include +#include +#include + +using TestUtilities::operator<<; // must be visible prior to the call site +#include +#include + +#include +#include +#include + +#include +#include +#include + +using namespace std; +using namespace CPPUNIT_NS; +using namespace RAPIDJSON_NAMESPACE; +using namespace IoUtilities; +using namespace ConversionUtilities; +using namespace TestUtilities; +using namespace TestUtilities::Literals; +using namespace ReflectiveRapidJSON; + +/// \cond + +// define some structs for testing serialization +struct TestObjectHana : public BinarySerializable { + BOOST_HANA_DEFINE_STRUCT(TestObjectHana, (int, number), (double, number2), (vector, numbers), (string, text), (bool, boolean)); +}; + +struct NestingObjectHana : public BinarySerializable { + BOOST_HANA_DEFINE_STRUCT(NestingObjectHana, (string, name), (TestObjectHana, testObj)); +}; + +struct NestingArrayHana : public BinarySerializable { + BOOST_HANA_DEFINE_STRUCT(NestingArrayHana, (string, name), (vector, testObjects)); +}; + +/// \endcond + +/*! + * \brief The BinaryReflectorBoostHanaTests class tests the integration of Boost.Hana with the (de)serializer. + * \remarks In these tests, the reflection is provided through Boost.Hana so the code generator is not involved. + */ +class BinaryReflectorBoostHanaTests : public TestFixture { + CPPUNIT_TEST_SUITE(BinaryReflectorBoostHanaTests); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp(); + void tearDown(); + +private: +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(BinaryReflectorBoostHanaTests); + +void BinaryReflectorBoostHanaTests::setUp() +{ +} + +void BinaryReflectorBoostHanaTests::tearDown() +{ +} diff --git a/lib/tests/binaryreflector.cpp b/lib/tests/binaryreflector.cpp new file mode 100644 index 0000000..86a6a16 --- /dev/null +++ b/lib/tests/binaryreflector.cpp @@ -0,0 +1,296 @@ +#include "../binary/reflector-chronoutilities.h" +#include "../binary/reflector.h" +#include "../binary/serializable.h" + +#include +#include +#include +#include + +using TestUtilities::operator<<; // must be visible prior to the call site +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace CPPUNIT_NS; +using namespace IoUtilities; +using namespace ChronoUtilities; +using namespace ConversionUtilities; +using namespace TestUtilities; +using namespace TestUtilities::Literals; +using namespace ReflectiveRapidJSON; + +/// \cond + +// define some enums and structs for testing serialization +enum SomeEnum { + SomeEnumItem1, + SomeEnumItem2, + SomeEnumItem3, +}; + +enum class SomeEnumClass : uint16 { + Item1, + Item2, + Item3, +}; + +struct TestObjectBinary : public BinarySerializable { + int number; + double number2; + vector numbers; + string text; + bool boolean; + map someMap; + unordered_map someHash; + set someSet; + multiset someMultiset; + unordered_set someUnorderedSet; + unordered_multiset someUnorderedMultiset; + SomeEnum someEnum; + SomeEnumClass someEnumClass; + TimeSpan timeSpan; + DateTime dateTime; +}; + +struct NestingArrayBinary : public BinarySerializable { + string name; + vector testObjects; +}; + +// pretend serialization code for structs has been generated +namespace ReflectiveRapidJSON { +namespace BinaryReflector { + +template <> void readCustomType(BinaryDeserializer &deserializer, TestObjectBinary &customType) +{ + deserializer.read(customType.number); + deserializer.read(customType.number2); + deserializer.read(customType.numbers); + deserializer.read(customType.text); + deserializer.read(customType.boolean); + deserializer.read(customType.someMap); + deserializer.read(customType.someHash); + deserializer.read(customType.someSet); + deserializer.read(customType.someMultiset); + deserializer.read(customType.someUnorderedSet); + deserializer.read(customType.someUnorderedMultiset); + deserializer.read(customType.someEnum); + deserializer.read(customType.someEnumClass); + deserializer.read(customType.timeSpan); + deserializer.read(customType.dateTime); +} + +template <> void writeCustomType(BinarySerializer &serializer, const TestObjectBinary &customType) +{ + serializer.write(customType.number); + serializer.write(customType.number2); + serializer.write(customType.numbers); + serializer.write(customType.text); + serializer.write(customType.boolean); + serializer.write(customType.someMap); + serializer.write(customType.someHash); + serializer.write(customType.someSet); + serializer.write(customType.someMultiset); + serializer.write(customType.someUnorderedSet); + serializer.write(customType.someUnorderedMultiset); + serializer.write(customType.someEnum); + serializer.write(customType.someEnumClass); + serializer.write(customType.timeSpan); + serializer.write(customType.dateTime); +} + +template <> void readCustomType(BinaryDeserializer &deserializer, NestingArrayBinary &customType) +{ + deserializer.read(customType.name); + deserializer.read(customType.testObjects); +} + +template <> void writeCustomType(BinarySerializer &serializer, const NestingArrayBinary &customType) +{ + serializer.write(customType.name); + serializer.write(customType.testObjects); +} + +} // namespace BinaryReflector + +// namespace BinaryReflector +} // namespace ReflectiveRapidJSON + +/// \endcond + +/*! + * \brief The BinaryReflectorTests class tests the (de)serializer. + * \remarks In these tests, the required reflection code is provided by hand so the generator isn't involved yet. + */ +class BinaryReflectorTests : public TestFixture { + CPPUNIT_TEST_SUITE(BinaryReflectorTests); + CPPUNIT_TEST(testSerializeSimpleStruct); + CPPUNIT_TEST(testDeserializeSimpleStruct); + CPPUNIT_TEST(testSerializeNestedStruct); + CPPUNIT_TEST(testDeserializeNestedStruct); + CPPUNIT_TEST_SUITE_END(); + +public: + BinaryReflectorTests(); + + void setUp(); + void tearDown(); + + void testSerializeSimpleStruct(); + void testDeserializeSimpleStruct(); + void testSerializeNestedStruct(); + void testDeserializeNestedStruct(); + void assertTestObject(const TestObjectBinary &deserialized); + +private: + vector m_buffer; + TestObjectBinary m_testObj; + NestingArrayBinary m_nestedTestObj; + vector m_expectedTestObj; + vector m_expectedNestedTestObj; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(BinaryReflectorTests); + +// clang-format off +BinaryReflectorTests::BinaryReflectorTests() + : m_buffer() + , m_testObj() + , m_nestedTestObj() + , m_expectedTestObj({ + 0x00, 0x00, 0x00, 0x05, + 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x85, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x05, + 0x89, + 0x73, 0x6F, 0x6D, 0x65, 0x20, 0x74, 0x65, 0x78, 0x74, + 0x01, + 0x82, + 0x83, 0x62, 0x61, 0x72, 0x00, 0x00, 0x00, 0x13, + 0x83, 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x11, + 0x80, + 0x83, + 0x81, 0x31, + 0x81, 0x32, + 0x81, 0x33, + 0x84, + 0x81, 0x31, + 0x81, 0x32, + 0x81, 0x32, + 0x81, 0x33, + 0x80, + 0x80, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAB, 0xCD, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF, 0xAB, + }) + , m_expectedNestedTestObj({ + 0x93, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6e, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, + 0x82, + }) +{ +} +// clang-format on + +void BinaryReflectorTests::setUp() +{ + m_testObj.number = 5; + m_testObj.number2 = 2.5; + m_testObj.numbers = { 1, 2, 3, 4, 5 }; + m_testObj.text = "some text"; + m_testObj.boolean = true; + m_testObj.someMap = { + { "foo", 17 }, + { "bar", 19 }, + }; + m_testObj.someSet = { "1", "2", "3", "2" }; + m_testObj.someMultiset = { "1", "2", "3", "2" }; + m_testObj.someEnum = SomeEnumItem2; + m_testObj.someEnumClass = SomeEnumClass::Item3; + m_testObj.timeSpan = TimeSpan(0xABCD); + m_testObj.dateTime = DateTime(0xEFAB); + m_nestedTestObj.name = "struct with nesting"; + m_expectedNestedTestObj.reserve(m_expectedNestedTestObj.size() + 2 * m_expectedTestObj.size()); + m_expectedNestedTestObj.insert(m_expectedNestedTestObj.end(), m_expectedTestObj.cbegin(), m_expectedTestObj.cend()); + m_expectedNestedTestObj.insert(m_expectedNestedTestObj.end(), m_expectedTestObj.cbegin(), m_expectedTestObj.cend()); + m_nestedTestObj.testObjects.insert(m_nestedTestObj.testObjects.end(), 2, m_testObj); +} + +void BinaryReflectorTests::tearDown() +{ +} + +void BinaryReflectorTests::testSerializeSimpleStruct() +{ + stringstream stream(ios_base::out | ios_base::binary); + stream.exceptions(ios_base::failbit | ios_base::badbit); + m_buffer.resize(m_expectedTestObj.size()); + stream.rdbuf()->pubsetbuf(reinterpret_cast(m_buffer.data()), static_cast(m_buffer.size())); + m_testObj.serialize(stream); + + CPPUNIT_ASSERT_EQUAL(m_expectedTestObj, m_buffer); +} + +void BinaryReflectorTests::testDeserializeSimpleStruct() +{ + stringstream stream(ios_base::in | ios_base::binary); + stream.exceptions(ios_base::failbit | ios_base::badbit); + stream.rdbuf()->pubsetbuf(reinterpret_cast(m_expectedTestObj.data()), static_cast(m_expectedTestObj.size())); + TestObjectBinary deserialized; + deserialized.deserialize(stream); + assertTestObject(deserialized); +} + +void BinaryReflectorTests::testSerializeNestedStruct() +{ + stringstream stream(ios_base::out | ios_base::binary); + stream.exceptions(ios_base::failbit | ios_base::badbit); + m_buffer.resize(m_expectedNestedTestObj.size()); + stream.rdbuf()->pubsetbuf(reinterpret_cast(m_buffer.data()), static_cast(m_buffer.size())); + m_nestedTestObj.serialize(stream); + + CPPUNIT_ASSERT_EQUAL(m_expectedNestedTestObj, m_buffer); +} + +void BinaryReflectorTests::testDeserializeNestedStruct() +{ + stringstream stream(ios_base::in | ios_base::binary); + stream.exceptions(ios_base::failbit | ios_base::badbit); + stream.rdbuf()->pubsetbuf(reinterpret_cast(m_expectedNestedTestObj.data()), static_cast(m_expectedNestedTestObj.size())); + NestingArrayBinary deserialized; + deserialized.deserialize(stream); + + CPPUNIT_ASSERT_EQUAL(m_nestedTestObj.name, deserialized.name); + for (const auto &testObj : deserialized.testObjects) { + assertTestObject(testObj); + } +} + +void BinaryReflectorTests::assertTestObject(const TestObjectBinary &deserialized) +{ + CPPUNIT_ASSERT_EQUAL(m_testObj.number, deserialized.number); + CPPUNIT_ASSERT_EQUAL(m_testObj.number2, deserialized.number2); + CPPUNIT_ASSERT_EQUAL(m_testObj.numbers, deserialized.numbers); + CPPUNIT_ASSERT_EQUAL(m_testObj.text, deserialized.text); + CPPUNIT_ASSERT_EQUAL(m_testObj.boolean, deserialized.boolean); + CPPUNIT_ASSERT_EQUAL(m_testObj.someMap, deserialized.someMap); + CPPUNIT_ASSERT_EQUAL(m_testObj.someHash, deserialized.someHash); + CPPUNIT_ASSERT_EQUAL(m_testObj.someSet, deserialized.someSet); + CPPUNIT_ASSERT_EQUAL(m_testObj.someMultiset, deserialized.someMultiset); + CPPUNIT_ASSERT_EQUAL(m_testObj.someUnorderedSet, deserialized.someUnorderedSet); + CPPUNIT_ASSERT_EQUAL(m_testObj.someUnorderedMultiset, deserialized.someUnorderedMultiset); +} diff --git a/lib/tests/jsonreflector.cpp b/lib/tests/jsonreflector.cpp index c1fc510..4020efa 100644 --- a/lib/tests/jsonreflector.cpp +++ b/lib/tests/jsonreflector.cpp @@ -30,23 +30,22 @@ using namespace TestUtilities; using namespace TestUtilities::Literals; using namespace ReflectiveRapidJSON; -// test traits -static_assert(JsonReflector::IsArray>::value, "vector mapped to array"); -static_assert(JsonReflector::IsArray>::value, "list mapped to array"); -static_assert(!JsonReflector::IsArray>::value, "set not considered an array"); -static_assert(!JsonReflector::IsArray>::value, "multiset not considered an array"); -static_assert(JsonReflector::IsArrayOrSet>::value, "set is array or set"); -static_assert(JsonReflector::IsArrayOrSet>::value, "multiset is array or set"); -static_assert(JsonReflector::IsSet>::value, "set"); -static_assert(JsonReflector::IsMultiSet>::value, "multiset"); -static_assert(!JsonReflector::IsArray::value, "string not mapped to array though it is iteratable"); -static_assert(JsonReflector::IsMapOrHash>::value, "map mapped to object"); -static_assert(JsonReflector::IsMapOrHash>::value, "hash mapped to object"); -static_assert(!JsonReflector::IsMapOrHash>::value, "vector not mapped to object"); - /// \cond -// define some structs for testing serialization +// define some enums and structs for testing serialization + +enum SomeEnum { + SomeEnumItem1, + SomeEnumItem2, + SomeEnumItem3, +}; + +enum class SomeEnumClass { + Item1, + Item2, + Item3, +}; + struct TestObject : public JsonSerializable { int number; double number2; @@ -71,18 +70,6 @@ struct NestingArray : public JsonSerializable { vector testObjects; }; -enum SomeEnum { - SomeEnumItem1, - SomeEnumItem2, - SomeEnumItem3, -}; - -enum class SomeEnumClass { - Item1, - Item2, - Item3, -}; - // pretend serialization code for structs has been generated namespace ReflectiveRapidJSON { namespace JsonReflector { diff --git a/lib/tests/traits.cpp b/lib/tests/traits.cpp new file mode 100644 index 0000000..9b66cd3 --- /dev/null +++ b/lib/tests/traits.cpp @@ -0,0 +1,21 @@ +#include "../traits.h" + +#include +#include + +using namespace std; +using namespace ReflectiveRapidJSON; + +// test traits +static_assert(IsArray>::value, "vector mapped to array"); +static_assert(IsArray>::value, "list mapped to array"); +static_assert(!IsArray>::value, "set not considered an array"); +static_assert(!IsArray>::value, "multiset not considered an array"); +static_assert(IsArrayOrSet>::value, "set is array or set"); +static_assert(IsArrayOrSet>::value, "multiset is array or set"); +static_assert(IsSet>::value, "set"); +static_assert(IsMultiSet>::value, "multiset"); +static_assert(!IsArray::value, "string not mapped to array though it is iteratable"); +static_assert(IsMapOrHash>::value, "map mapped to object"); +static_assert(IsMapOrHash>::value, "hash mapped to object"); +static_assert(!IsMapOrHash>::value, "vector not mapped to object"); diff --git a/lib/traits.h b/lib/traits.h new file mode 100644 index 0000000..b6b048a --- /dev/null +++ b/lib/traits.h @@ -0,0 +1,31 @@ +#ifndef REFLECTIVE_RAPIDJSON_TRAITS +#define REFLECTIVE_RAPIDJSON_TRAITS + +#include + +#include +#include +#include +#include +#include + +namespace ReflectiveRapidJSON { + +// define traits to check for arrays, sets and maps +template +using IsMapOrHash = Traits::Any, Traits::IsSpecializationOf>; +template using IsSet = Traits::Any, Traits::IsSpecializationOf>; +template +using IsMultiSet = Traits::Any, Traits::IsSpecializationOf>; +template +using IsArrayOrSet + = Traits::All, Traits::Not>, Traits::Not>>; +template +using IsArray = Traits::All, Traits::Not>, + Traits::Not>, Traits::Not>, Traits::Not>>; +template +using IsIteratableExceptString = Traits::All, Traits::Not>>; + +} // namespace ReflectiveRapidJSON + +#endif // REFLECTIVE_RAPIDJSON_TRAITS