Add library for basic binary (de)serialization
This commit is contained in:
parent
551ead193e
commit
316c1ba838
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
||||
{
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
|
@ -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
|
Loading…
Reference in New Issue