Allow using Boost.Hana instead of code generator
This commit is contained in:
parent
c026dafff7
commit
0d17d5f20b
|
@ -3,8 +3,12 @@
|
|||
The main goal of this project is to provide a code generator for serializing/deserializing C++ objects to/from JSON
|
||||
using Clang and RapidJSON.
|
||||
|
||||
However, extending the generator to generate code for other applications of reflection or provide generic
|
||||
functionallity would be possible as well.
|
||||
However, extending the generator to generate code for other applications of reflection or to provide generic
|
||||
reflection functionallity would be possible as well.
|
||||
|
||||
This repository also contains a small, additional header to use RapidJSON with Boost.Hana. This allows to serialize
|
||||
or dezerialize simple data structures using the `BOOST_HANA_DEFINE_STRUCT` macro rather than requiring the code
|
||||
generator.
|
||||
|
||||
## Usage
|
||||
This example shows how the library can be used to make a `struct` serializable:
|
||||
|
|
|
@ -6,6 +6,7 @@ set(META_PROJECT_TYPE library)
|
|||
# add project files
|
||||
set(HEADER_FILES
|
||||
jsonreflector.h
|
||||
jsonreflector-boosthana.h
|
||||
jsonserializable.h
|
||||
)
|
||||
set(SRC_FILES
|
||||
|
@ -15,6 +16,7 @@ set(TEST_HEADER_FILES
|
|||
set(TEST_SRC_FILES
|
||||
tests/cppunit.cpp
|
||||
tests/jsonreflector.cpp
|
||||
tests/jsonreflector-boosthana.cpp
|
||||
)
|
||||
set(CMAKE_MODULE_FILES
|
||||
cmake/modules/ReflectionGenerator.cmake
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
#ifndef REFLECTIVE_RAPIDJSON_JSON_REFLECTOR_BOOST_HANA_H
|
||||
#define REFLECTIVE_RAPIDJSON_JSON_REFLECTOR_BOOST_HANA_H
|
||||
|
||||
/*!
|
||||
* \file jsonreflector-boosthana.h
|
||||
* \brief Contains generic functions relying on Boost.Hana which can replace the code which would
|
||||
* otherwise had to be generated.
|
||||
*/
|
||||
|
||||
#include "./jsonreflector.h"
|
||||
|
||||
// TODO: find out which header files are actually relevant rather than including the master
|
||||
#include <boost/hana.hpp>
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
namespace Reflector {
|
||||
|
||||
// define functions to "push" values to a RapidJSON array or object
|
||||
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
boost::hana::for_each(reflectable, [&value, &allocator](auto pair) {
|
||||
push(boost::hana::second(pair), boost::hana::to<char const*>(boost::hana::first(pair)), value, allocator);
|
||||
});
|
||||
}
|
||||
|
||||
// define functions to "pull" values from a RapidJSON array or object
|
||||
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
void pull(Type &reflectable, RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value)
|
||||
{
|
||||
boost::hana::for_each(reflectable, [&value](auto pair) {
|
||||
pull(boost::hana::second(pair), boost::hana::to<char const*>(boost::hana::first(pair)), value);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value)
|
||||
{
|
||||
boost::hana::for_each(reflectable, [&value](auto pair) {
|
||||
pull(boost::hana::second(pair), boost::hana::to<char const*>(boost::hana::first(pair)), value);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Reflector
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_JSON_REFLECTOR_BOOST_HANA_H
|
|
@ -0,0 +1,245 @@
|
|||
#include "../jsonreflector-boosthana.h"
|
||||
#include "../jsonserializable.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 TestObject : public JSONSerializable<TestObject> {
|
||||
BOOST_HANA_DEFINE_STRUCT(TestObject,
|
||||
(int, number),
|
||||
(double, number2),
|
||||
(vector<int>, numbers),
|
||||
(string, text),
|
||||
(bool, boolean)
|
||||
);
|
||||
};
|
||||
|
||||
struct NestingObject : public JSONSerializable<NestingObject> {
|
||||
BOOST_HANA_DEFINE_STRUCT(NestingObject,
|
||||
(string, name),
|
||||
(TestObject, testObj)
|
||||
);
|
||||
};
|
||||
|
||||
struct NestingArray : public JSONSerializable<NestingArray> {
|
||||
BOOST_HANA_DEFINE_STRUCT(NestingArray,
|
||||
(string, name),
|
||||
(vector<TestObject>, testObjects)
|
||||
);
|
||||
};
|
||||
|
||||
/// \endcond
|
||||
|
||||
/*!
|
||||
* \brief The ReflectorTests class tests RapidJSON wrapper which is used to ease code generation.
|
||||
* \remarks In this tests, no reflection or code generation is involved yet.
|
||||
*/
|
||||
class JSONReflectorBoostHanaTests : public TestFixture {
|
||||
CPPUNIT_TEST_SUITE(JSONReflectorBoostHanaTests);
|
||||
CPPUNIT_TEST(testSerializePrimitives);
|
||||
CPPUNIT_TEST(testSerializeSimpleObjects);
|
||||
CPPUNIT_TEST(testSerializeNestedObjects);
|
||||
CPPUNIT_TEST(testDeserializePrimitives);
|
||||
CPPUNIT_TEST(testDeserializeSimpleObjects);
|
||||
CPPUNIT_TEST(testDeserializeNestedObjects);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
void testSerializePrimitives();
|
||||
void testSerializeSimpleObjects();
|
||||
void testSerializeNestedObjects();
|
||||
void testDeserializePrimitives();
|
||||
void testDeserializeSimpleObjects();
|
||||
void testDeserializeNestedObjects();
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(JSONReflectorBoostHanaTests);
|
||||
|
||||
void JSONReflectorBoostHanaTests::setUp()
|
||||
{
|
||||
}
|
||||
|
||||
void JSONReflectorBoostHanaTests::tearDown()
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests serializing strings, numbers, arrays and boolean.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testSerializePrimitives()
|
||||
{
|
||||
Document doc(kArrayType);
|
||||
Document::AllocatorType &alloc = doc.GetAllocator();
|
||||
doc.SetArray();
|
||||
Document::Array array(doc.GetArray());
|
||||
|
||||
// string
|
||||
Reflector::push<string>("foo"s, array, alloc);
|
||||
Reflector::push<const char *>("bar", array, alloc);
|
||||
// number
|
||||
Reflector::push<int>(25, array, alloc);
|
||||
Reflector::push<double>(12.5, array, alloc);
|
||||
// array
|
||||
Reflector::push<vector<const char *>>({ "foo1", "bar1" }, array, alloc);
|
||||
Reflector::push<list<const char *>>({ "foo2", "bar2" }, array, alloc);
|
||||
Reflector::push<initializer_list<const char *>>({ "foo3", "bar3" }, array, alloc);
|
||||
// boolean
|
||||
Reflector::push<bool>(true, array, alloc);
|
||||
Reflector::push<bool>(false, array, alloc);
|
||||
|
||||
StringBuffer strbuf;
|
||||
Writer<StringBuffer> jsonWriter(strbuf);
|
||||
doc.Accept(jsonWriter);
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
"[\"foo\",\"bar\",25,12.5,[\"foo1\",\"bar1\"],[\"foo2\",\"bar2\"],[\"foo3\",\"bar3\"],true,false]"s, string(strbuf.GetString()));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests serializing objects.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testSerializeSimpleObjects()
|
||||
{
|
||||
TestObject testObj;
|
||||
testObj.number = 42;
|
||||
testObj.number2 = 3.141592653589793;
|
||||
testObj.numbers = { 1, 2, 3, 4 };
|
||||
testObj.text = "test";
|
||||
testObj.boolean = false;
|
||||
CPPUNIT_ASSERT_EQUAL("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}"s,
|
||||
string(testObj.toJson().GetString()));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests serializing nested object and arrays.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testSerializeNestedObjects()
|
||||
{
|
||||
NestingObject nestingObj;
|
||||
nestingObj.name = "nesting";
|
||||
TestObject &testObj = nestingObj.testObj;
|
||||
testObj.number = 42;
|
||||
testObj.number2 = 3.141592653589793;
|
||||
testObj.numbers = { 1, 2, 3, 4 };
|
||||
testObj.text = "test";
|
||||
testObj.boolean = false;
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
"{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}}"s,
|
||||
string(nestingObj.toJson().GetString()));
|
||||
NestingArray nestingArray;
|
||||
nestingArray.name = "nesting2";
|
||||
nestingArray.testObjects.emplace_back(testObj);
|
||||
nestingArray.testObjects.emplace_back(testObj);
|
||||
nestingArray.testObjects.back().number = 43;
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
"{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false},{\"number\":43,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}]}"s,
|
||||
string(nestingArray.toJson().GetString()));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests deserializing strings, numbers (int, float, double) and boolean.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testDeserializePrimitives()
|
||||
{
|
||||
Document doc(kArrayType);
|
||||
|
||||
doc.Parse("[\"a\", 5, 5e6, \"test\", true, 4.125, false]");
|
||||
auto array = doc.GetArray().begin();
|
||||
|
||||
string str1, str2;
|
||||
int int1 = 0;
|
||||
bool bool1 = false, bool2 = true;
|
||||
float float1 = 0.0;
|
||||
double double1 = 0.0;
|
||||
Reflector::pull(str1, array);
|
||||
Reflector::pull(int1, array);
|
||||
Reflector::pull(float1, array);
|
||||
Reflector::pull(str2, array);
|
||||
Reflector::pull(bool1, array);
|
||||
Reflector::pull(double1, array);
|
||||
Reflector::pull(bool2, array);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL("a"s, str1);
|
||||
CPPUNIT_ASSERT_EQUAL(5, int1);
|
||||
CPPUNIT_ASSERT_EQUAL(5e6f, float1);
|
||||
CPPUNIT_ASSERT_EQUAL("test"s, str2);
|
||||
CPPUNIT_ASSERT_EQUAL(true, bool1);
|
||||
CPPUNIT_ASSERT_EQUAL(4.125, double1);
|
||||
CPPUNIT_ASSERT_EQUAL(false, bool2);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests deserializing simple objects.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testDeserializeSimpleObjects()
|
||||
{
|
||||
const TestObject testObj(
|
||||
TestObject::fromJson("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}"));
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(42, testObj.number);
|
||||
CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2);
|
||||
CPPUNIT_ASSERT_EQUAL(vector<int>({ 1, 2, 3, 4 }), testObj.numbers);
|
||||
CPPUNIT_ASSERT_EQUAL("test"s, testObj.text);
|
||||
CPPUNIT_ASSERT_EQUAL(false, testObj.boolean);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests deserializing nested objects and arrays.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testDeserializeNestedObjects()
|
||||
{
|
||||
const NestingObject nestingObj(NestingObject::fromJson("{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,"
|
||||
"\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}}"));
|
||||
const TestObject &testObj = nestingObj.testObj;
|
||||
CPPUNIT_ASSERT_EQUAL("nesting"s, nestingObj.name);
|
||||
CPPUNIT_ASSERT_EQUAL(42, testObj.number);
|
||||
CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2);
|
||||
CPPUNIT_ASSERT_EQUAL(vector<int>({ 1, 2, 3, 4 }), testObj.numbers);
|
||||
CPPUNIT_ASSERT_EQUAL("test"s, testObj.text);
|
||||
CPPUNIT_ASSERT_EQUAL(false, testObj.boolean);
|
||||
|
||||
const NestingArray nestingArray(NestingArray::fromJson("{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,"
|
||||
"\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false},{\"number\":43,\"number2\":3."
|
||||
"141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}]}"));
|
||||
const vector<TestObject> &testObjects = nestingArray.testObjects;
|
||||
CPPUNIT_ASSERT_EQUAL("nesting2"s, nestingArray.name);
|
||||
CPPUNIT_ASSERT_EQUAL(2_st, testObjects.size());
|
||||
CPPUNIT_ASSERT_EQUAL(42, testObjects[0].number);
|
||||
CPPUNIT_ASSERT_EQUAL(43, testObjects[1].number);
|
||||
for (const TestObject &testObj : testObjects) {
|
||||
CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2);
|
||||
CPPUNIT_ASSERT_EQUAL(vector<int>({ 1, 2, 3, 4 }), testObj.numbers);
|
||||
CPPUNIT_ASSERT_EQUAL("test"s, testObj.text);
|
||||
CPPUNIT_ASSERT_EQUAL(false, testObj.boolean);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue