Add library for basic binary (de)serialization

This commit is contained in:
Martchus 2018-06-23 14:35:43 +02:00
parent 551ead193e
commit 316c1ba838
11 changed files with 744 additions and 30 deletions

View File

@ -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

View File

@ -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)

View File

@ -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 <boost/hana/adapt_struct.hpp>
#include <boost/hana/at_key.hpp>
#include <boost/hana/define_struct.hpp>
#include <boost/hana/for_each.hpp>
#include <boost/hana/intersection.hpp>
#include <boost/hana/keys.hpp>
namespace BinaryReflector {
namespace JsonReflector {
} // namespace JsonReflector
} // namespace BinaryReflector
#endif // REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_BOOST_HANA_H

View File

@ -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 <c++utilities/chrono/datetime.h>
#include <c++utilities/chrono/timespan.h>
namespace ReflectiveRapidJSON {
namespace BinaryReflector {
template <> void readCustomType<ChronoUtilities::DateTime>(BinaryDeserializer &deserializer, ChronoUtilities::DateTime &dateTime)
{
deserializer.read(dateTime.ticks());
}
template <> void writeCustomType<ChronoUtilities::DateTime>(BinarySerializer &serializer, const ChronoUtilities::DateTime &dateTime)
{
serializer.write(dateTime.totalTicks());
}
template <> void readCustomType<ChronoUtilities::TimeSpan>(BinaryDeserializer &deserializer, ChronoUtilities::TimeSpan &timeSpan)
{
deserializer.read(timeSpan.ticks());
}
template <> void writeCustomType<ChronoUtilities::TimeSpan>(BinarySerializer &serializer, const ChronoUtilities::TimeSpan &timeSpan)
{
serializer.write(timeSpan.totalTicks());
}
} // namespace BinaryReflector
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_CHRONO_UTILITIES_H

168
lib/binary/reflector.h Normal file
View File

@ -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 <c++utilities/conversion/types.h>
#include <c++utilities/io/binaryreader.h>
#include <c++utilities/io/binarywriter.h>
#include <limits>
#include <memory>
#include <string>
#include <tuple>
namespace ReflectiveRapidJSON {
/*!
* \brief The AdaptedBinarySerializable class allows considering 3rd party classes as serializable.
*/
template <typename T> struct AdaptedBinarySerializable : public Traits::Bool<false> {
static constexpr const char *name = "AdaptedBinarySerializable";
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::AdaptedBinarySerializable";
};
template <typename Type> 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 <typename Type>
using IsBuiltInType = Traits::Any<Traits::IsAnyOf<Type, char, byte, bool, std::string, int16, uint16, int32, uint32, int64, uint64, float32, float64>,
Traits::IsIteratable<Type>, Traits::IsSpecializationOf<Type, std::pair>, std::is_enum<Type>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
template <typename Type>
using IsSerializable = Traits::All<
Traits::Any<Traits::Not<Traits::IsComplete<Type>>, std::is_base_of<BinarySerializable<Type>, Type>, AdaptedBinarySerializable<Type>>,
Traits::Not<IsBuiltInType<Type>>>;
class BinaryDeserializer;
class BinarySerializer;
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void readCustomType(BinaryDeserializer &deserializer, Type &customType);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void writeCustomType(BinarySerializer &serializer, const Type &customType);
class BinaryDeserializer : public IoUtilities::BinaryReader {
public:
BinaryDeserializer(std::istream *stream);
using IoUtilities::BinaryReader::read;
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr> void read(Type &pair);
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsResizable<Type>> * = nullptr> void read(Type &iteratable);
template <typename Type, Traits::EnableIf<IsMapOrHash<Type>> * = nullptr> void read(Type &iteratable);
template <typename Type,
Traits::EnableIf<IsIteratableExceptString<Type>, Traits::None<IsMapOrHash<Type>, Traits::All<IsArray<Type>, Traits::IsResizable<Type>>>>
* = nullptr>
void read(Type &iteratable);
template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void read(Type &customType);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void read(Type &customType);
};
class BinarySerializer : public IoUtilities::BinaryWriter {
public:
BinarySerializer(std::ostream *stream);
using IoUtilities::BinaryWriter::write;
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr> void write(const Type &pair);
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 &customType);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void write(const Type &customType);
};
inline BinaryDeserializer::BinaryDeserializer(std::istream *stream)
: IoUtilities::BinaryReader(stream)
{
}
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> *> void BinaryDeserializer::read(Type &pair)
{
read(pair.first);
read(pair.second);
}
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsResizable<Type>> *> void BinaryDeserializer::read(Type &iteratable)
{
const auto size = readVariableLengthUIntBE();
iteratable.resize(size);
for (auto &element : iteratable) {
read(element);
}
}
template <typename Type, Traits::EnableIf<IsMapOrHash<Type>> *> void BinaryDeserializer::read(Type &iteratable)
{
const auto size = readVariableLengthUIntBE();
for (size_t i = 0; i != size; ++i) {
std::pair<typename std::remove_const<typename Type::value_type::first_type>::type, typename Type::value_type::second_type> value;
read(value);
iteratable.emplace(std::move(value));
}
}
template <typename Type,
Traits::EnableIf<IsIteratableExceptString<Type>, Traits::None<IsMapOrHash<Type>, Traits::All<IsArray<Type>, Traits::IsResizable<Type>>>> *>
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 <typename Type, Traits::EnableIf<std::is_enum<Type>> *> void BinaryDeserializer::read(Type &enumValue)
{
typename std::underlying_type<Type>::type value;
read(value);
enumValue = static_cast<Type>(value);
}
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinaryDeserializer::read(Type &customType)
{
readCustomType(*this, customType);
}
inline BinarySerializer::BinarySerializer(std::ostream *stream)
: IoUtilities::BinaryWriter(stream)
{
}
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> *> void BinarySerializer::write(const Type &pair)
{
write(pair.first);
write(pair.second);
}
template <typename Type, Traits::EnableIf<IsIteratableExceptString<Type>, Traits::HasSize<Type>> *>
void BinarySerializer::write(const Type &iteratable)
{
writeVariableLengthUIntBE(iteratable.size());
for (const auto &element : iteratable) {
write(element);
}
}
template <typename Type, Traits::EnableIf<std::is_enum<Type>> *> void BinarySerializer::write(const Type &enumValue)
{
write(static_cast<typename std::underlying_type<Type>::type>(enumValue));
}
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinarySerializer::write(const Type &customType)
{
writeCustomType(*this, customType);
}
} // namespace BinaryReflector
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_H

50
lib/binary/serializable.h Normal file
View File

@ -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 <iosfwd>
#include <string>
namespace ReflectiveRapidJSON {
/*!
* \brief The BinarySerializable class provides the CRTP-base for (de)serializable objects.
*/
template <typename Type> struct BinarySerializable {
void serialize(std::ostream &outputStream) const;
void deserialize(std::istream &inputStream);
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::BinarySerializable";
};
template <typename Type> inline void BinarySerializable<Type>::serialize(std::ostream &outputStream) const
{
BinaryReflector::BinarySerializer(&outputStream).write(static_cast<const Type &>(*this));
}
template <typename Type> inline void BinarySerializable<Type>::deserialize(std::istream &inputStream)
{
BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast<Type &>(*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<T> : Traits::Bool<true> { \
}
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_BINARY_SERIALIZABLE_H

View File

@ -0,0 +1,70 @@
#include "../binary/reflector-boosthana.h"
#include "../binary/serializable.h"
#include <c++utilities/conversion/stringbuilder.h>
#include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/io/misc.h>
#include <c++utilities/tests/testutils.h>
using TestUtilities::operator<<; // must be visible prior to the call site
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <rapidjson/document.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
#include <iostream>
#include <string>
#include <vector>
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<TestObjectHana> {
BOOST_HANA_DEFINE_STRUCT(TestObjectHana, (int, number), (double, number2), (vector<int>, numbers), (string, text), (bool, boolean));
};
struct NestingObjectHana : public BinarySerializable<NestingObjectHana> {
BOOST_HANA_DEFINE_STRUCT(NestingObjectHana, (string, name), (TestObjectHana, testObj));
};
struct NestingArrayHana : public BinarySerializable<NestingArrayHana> {
BOOST_HANA_DEFINE_STRUCT(NestingArrayHana, (string, name), (vector<TestObjectHana>, 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()
{
}

View File

@ -0,0 +1,296 @@
#include "../binary/reflector-chronoutilities.h"
#include "../binary/reflector.h"
#include "../binary/serializable.h"
#include <c++utilities/conversion/stringbuilder.h>
#include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/io/misc.h>
#include <c++utilities/tests/testutils.h>
using TestUtilities::operator<<; // must be visible prior to the call site
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
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<TestObjectBinary> {
int number;
double number2;
vector<int> numbers;
string text;
bool boolean;
map<string, int> someMap;
unordered_map<string, bool> someHash;
set<string> someSet;
multiset<string> someMultiset;
unordered_set<string> someUnorderedSet;
unordered_multiset<string> someUnorderedMultiset;
SomeEnum someEnum;
SomeEnumClass someEnumClass;
TimeSpan timeSpan;
DateTime dateTime;
};
struct NestingArrayBinary : public BinarySerializable<NestingArrayBinary> {
string name;
vector<TestObjectBinary> testObjects;
};
// pretend serialization code for structs has been generated
namespace ReflectiveRapidJSON {
namespace BinaryReflector {
template <> void readCustomType<TestObjectBinary>(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<TestObjectBinary>(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<NestingArrayBinary>(BinaryDeserializer &deserializer, NestingArrayBinary &customType)
{
deserializer.read(customType.name);
deserializer.read(customType.testObjects);
}
template <> void writeCustomType<NestingArrayBinary>(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<byte> m_buffer;
TestObjectBinary m_testObj;
NestingArrayBinary m_nestedTestObj;
vector<byte> m_expectedTestObj;
vector<byte> 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<char *>(m_buffer.data()), static_cast<streamsize>(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<char *>(m_expectedTestObj.data()), static_cast<streamsize>(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<char *>(m_buffer.data()), static_cast<streamsize>(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<char *>(m_expectedNestedTestObj.data()), static_cast<streamsize>(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);
}

View File

@ -30,23 +30,22 @@ using namespace TestUtilities;
using namespace TestUtilities::Literals;
using namespace ReflectiveRapidJSON;
// test traits
static_assert(JsonReflector::IsArray<vector<int>>::value, "vector mapped to array");
static_assert(JsonReflector::IsArray<list<int>>::value, "list mapped to array");
static_assert(!JsonReflector::IsArray<set<int>>::value, "set not considered an array");
static_assert(!JsonReflector::IsArray<multiset<int>>::value, "multiset not considered an array");
static_assert(JsonReflector::IsArrayOrSet<set<int>>::value, "set is array or set");
static_assert(JsonReflector::IsArrayOrSet<multiset<int>>::value, "multiset is array or set");
static_assert(JsonReflector::IsSet<unordered_set<int>>::value, "set");
static_assert(JsonReflector::IsMultiSet<unordered_multiset<int>>::value, "multiset");
static_assert(!JsonReflector::IsArray<string>::value, "string not mapped to array though it is iteratable");
static_assert(JsonReflector::IsMapOrHash<map<string, int>>::value, "map mapped to object");
static_assert(JsonReflector::IsMapOrHash<unordered_map<string, int>>::value, "hash mapped to object");
static_assert(!JsonReflector::IsMapOrHash<vector<int>>::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<TestObject> {
int number;
double number2;
@ -71,18 +70,6 @@ struct NestingArray : public JsonSerializable<NestingArray> {
vector<TestObject> testObjects;
};
enum SomeEnum {
SomeEnumItem1,
SomeEnumItem2,
SomeEnumItem3,
};
enum class SomeEnumClass {
Item1,
Item2,
Item3,
};
// pretend serialization code for structs has been generated
namespace ReflectiveRapidJSON {
namespace JsonReflector {

21
lib/tests/traits.cpp Normal file
View File

@ -0,0 +1,21 @@
#include "../traits.h"
#include <list>
#include <vector>
using namespace std;
using namespace ReflectiveRapidJSON;
// test traits
static_assert(IsArray<vector<int>>::value, "vector mapped to array");
static_assert(IsArray<list<int>>::value, "list mapped to array");
static_assert(!IsArray<set<int>>::value, "set not considered an array");
static_assert(!IsArray<multiset<int>>::value, "multiset not considered an array");
static_assert(IsArrayOrSet<set<int>>::value, "set is array or set");
static_assert(IsArrayOrSet<multiset<int>>::value, "multiset is array or set");
static_assert(IsSet<unordered_set<int>>::value, "set");
static_assert(IsMultiSet<unordered_multiset<int>>::value, "multiset");
static_assert(!IsArray<string>::value, "string not mapped to array though it is iteratable");
static_assert(IsMapOrHash<map<string, int>>::value, "map mapped to object");
static_assert(IsMapOrHash<unordered_map<string, int>>::value, "hash mapped to object");
static_assert(!IsMapOrHash<vector<int>>::value, "vector not mapped to object");

31
lib/traits.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef REFLECTIVE_RAPIDJSON_TRAITS
#define REFLECTIVE_RAPIDJSON_TRAITS
#include <c++utilities/misc/traits.h>
#include <map>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>
namespace ReflectiveRapidJSON {
// define traits to check for arrays, sets and maps
template <typename Type>
using IsMapOrHash = Traits::Any<Traits::IsSpecializationOf<Type, std::map>, Traits::IsSpecializationOf<Type, std::unordered_map>>;
template <typename Type> using IsSet = Traits::Any<Traits::IsSpecializationOf<Type, std::set>, Traits::IsSpecializationOf<Type, std::unordered_set>>;
template <typename Type>
using IsMultiSet = Traits::Any<Traits::IsSpecializationOf<Type, std::multiset>, Traits::IsSpecializationOf<Type, std::unordered_multiset>>;
template <typename Type>
using IsArrayOrSet
= Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>, Traits::Not<IsMapOrHash<Type>>>;
template <typename Type>
using IsArray = Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>,
Traits::Not<IsMapOrHash<Type>>, Traits::Not<IsSet<Type>>, Traits::Not<IsMultiSet<Type>>>;
template <typename Type>
using IsIteratableExceptString = Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>;
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_TRAITS