diff --git a/README.md b/README.md index 51f7eb1..2639abb 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 84221b3..a79198c 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -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 diff --git a/lib/jsonreflector-boosthana.h b/lib/jsonreflector-boosthana.h new file mode 100644 index 0000000..b107df2 --- /dev/null +++ b/lib/jsonreflector-boosthana.h @@ -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 + +namespace ReflectiveRapidJSON { +namespace Reflector { + +// define functions to "push" values to a RapidJSON array or object + +template , std::is_floating_point, std::is_pointer, + Traits::All, Traits::Not>>>...> +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(boost::hana::first(pair)), value, allocator); + }); +} + +// define functions to "pull" values from a RapidJSON array or object + +template , std::is_floating_point, std::is_pointer, + Traits::All, Traits::Not>>>...> +void pull(Type &reflectable, RAPIDJSON_NAMESPACE::GenericValue>::ValueIterator &value) +{ + boost::hana::for_each(reflectable, [&value](auto pair) { + pull(boost::hana::second(pair), boost::hana::to(boost::hana::first(pair)), value); + }); +} + +template , std::is_floating_point, std::is_pointer, + Traits::All, Traits::Not>>>...> +void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue>::ConstObject &value) +{ + boost::hana::for_each(reflectable, [&value](auto pair) { + pull(boost::hana::second(pair), boost::hana::to(boost::hana::first(pair)), value); + }); +} + +} // namespace Reflector +} // namespace ReflectiveRapidJSON + +#endif // REFLECTIVE_RAPIDJSON_JSON_REFLECTOR_BOOST_HANA_H diff --git a/lib/tests/jsonreflector-boosthana.cpp b/lib/tests/jsonreflector-boosthana.cpp new file mode 100644 index 0000000..a9f3e2a --- /dev/null +++ b/lib/tests/jsonreflector-boosthana.cpp @@ -0,0 +1,245 @@ +#include "../jsonreflector-boosthana.h" +#include "../jsonserializable.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 TestObject : public JSONSerializable { + BOOST_HANA_DEFINE_STRUCT(TestObject, + (int, number), + (double, number2), + (vector, numbers), + (string, text), + (bool, boolean) + ); +}; + +struct NestingObject : public JSONSerializable { + BOOST_HANA_DEFINE_STRUCT(NestingObject, + (string, name), + (TestObject, testObj) + ); +}; + +struct NestingArray : public JSONSerializable { + BOOST_HANA_DEFINE_STRUCT(NestingArray, + (string, name), + (vector, 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("foo"s, array, alloc); + Reflector::push("bar", array, alloc); + // number + Reflector::push(25, array, alloc); + Reflector::push(12.5, array, alloc); + // array + Reflector::push>({ "foo1", "bar1" }, array, alloc); + Reflector::push>({ "foo2", "bar2" }, array, alloc); + Reflector::push>({ "foo3", "bar3" }, array, alloc); + // boolean + Reflector::push(true, array, alloc); + Reflector::push(false, array, alloc); + + StringBuffer strbuf; + Writer 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({ 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({ 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 &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({ 1, 2, 3, 4 }), testObj.numbers); + CPPUNIT_ASSERT_EQUAL("test"s, testObj.text); + CPPUNIT_ASSERT_EQUAL(false, testObj.boolean); + } +}