Consider base classes when generating code for (de)serialization

This commit is contained in:
Martchus 2017-10-25 16:47:14 +02:00
parent 617ee58b91
commit 3008e3ad6e
4 changed files with 117 additions and 0 deletions

View File

@ -84,9 +84,18 @@ void JSONSerializationCodeGenerator::generate(ostream &os) const
// add push and pull functions for each class, for an example of the resulting
// output, see ../lib/tests/jsonserializable.cpp (code under comment "pretend serialization code...")
for (const RelevantClass &relevantClass : m_relevantClasses) {
// write comment
os << "// define code for (de)serializing " << relevantClass.qualifiedName << " objects\n";
// find relevant base classes
const vector<const RelevantClass *> relevantBases = findRelevantBaseClasses(relevantClass);
// print push method
os << "template <> inline void push<::" << relevantClass.qualifiedName << ">(const ::" << relevantClass.qualifiedName
<< " &reflectable, ::RAPIDJSON_NAMESPACE::Value::Object &value, ::RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)\n{\n";
for (const RelevantClass *baseClass : relevantBases) {
os << " push(static_cast<const ::" << baseClass->qualifiedName << " &>(reflectable), value, allocator);\n";
}
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
os << " push(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, allocator);\n";
}
@ -95,6 +104,9 @@ void JSONSerializationCodeGenerator::generate(ostream &os) const
// print pull method
os << "template <> inline void pull<::" << relevantClass.qualifiedName << ">(::" << relevantClass.qualifiedName
<< " &reflectable, const ::RAPIDJSON_NAMESPACE::GenericValue<::RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value)\n{\n";
for (const RelevantClass *baseClass : relevantBases) {
os << " pull(static_cast<::" << baseClass->qualifiedName << " &>(reflectable), value);\n";
}
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
os << " pull(reflectable." << field->getName() << ", \"" << field->getName() << "\", value);\n";
}
@ -106,4 +118,16 @@ void JSONSerializationCodeGenerator::generate(ostream &os) const
"} // namespace ReflectiveRapidJSON\n";
}
std::vector<const JSONSerializationCodeGenerator::RelevantClass *> JSONSerializationCodeGenerator::findRelevantBaseClasses(
const RelevantClass &relevantClass) const
{
vector<const RelevantClass *> relevantBaseClasses;
for (const RelevantClass &otherClass : m_relevantClasses) {
if (relevantClass.record->isDerivedFrom(otherClass.record)) {
relevantBaseClasses.push_back(&otherClass);
}
}
return relevantBaseClasses;
}
} // namespace ReflectiveRapidJSON

View File

@ -65,6 +65,8 @@ private:
clang::CXXRecordDecl *record;
};
std::vector<const RelevantClass *> findRelevantBaseClasses(const RelevantClass &relevantClass) const;
std::vector<RelevantClass> m_relevantClasses;
};

View File

@ -1,6 +1,7 @@
namespace ReflectiveRapidJSON {
namespace Reflector {
// define code for (de)serializing TestNamespace1::Person objects
template <> inline void push<::TestNamespace1::Person>(const ::TestNamespace1::Person &reflectable, ::RAPIDJSON_NAMESPACE::Value::Object &value, ::RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
push(reflectable.age, "age", value, allocator);

View File

@ -34,6 +34,40 @@ struct TestStruct : public JSONSerializable<TestStruct> {
string yetAnotherString = "bar";
};
/*!
* \brief The AnotherTestStruct struct inherits from JSONSerializable and should hence have functional fromJson()
* and toJson() methods. This is asserted in OverallTests::testInheritence();
*/
struct AnotherTestStruct : public JSONSerializable<AnotherTestStruct> {
vector<string> arrayOfStrings{ "a", "b", "cd" };
};
/*!
* \brief The DerivedTestStruct struct inherits from JSONSerializable and should hence have functional fromJson()
* and toJson() methods. This is asserted in OverallTests::testInheritence();
*/
struct DerivedTestStruct : public TestStruct, public JSONSerializable<DerivedTestStruct> {
bool someBool = true;
};
/*!
* \brief The NonSerializable struct should be ignored when used as base class because it isn't serializable.
*/
struct NonSerializable {
int ignored = 25;
};
/*!
* \brief The MultipleDerivedTestStruct struct inherits from JSONSerializable and should hence have functional fromJson()
* and toJson() methods. This is asserted in OverallTests::testInheritence();
*/
struct MultipleDerivedTestStruct : public TestStruct,
public AnotherTestStruct,
public NonSerializable,
public JSONSerializable<MultipleDerivedTestStruct> {
bool someBool = false;
};
/*!
* \brief The OverallTests class tests the overall functionality of the code generator (CLI and generator itself).
*/
@ -42,6 +76,8 @@ class OverallTests : public TestFixture {
CPPUNIT_TEST(testGeneratorItself);
CPPUNIT_TEST(testCLI);
CPPUNIT_TEST(testIncludingGeneratedHeader);
CPPUNIT_TEST(testSingleInheritence);
CPPUNIT_TEST(testMultipleInheritence);
CPPUNIT_TEST_SUITE_END();
public:
@ -51,6 +87,8 @@ public:
void testGeneratorItself();
void testCLI();
void testIncludingGeneratedHeader();
void testSingleInheritence();
void testMultipleInheritence();
private:
vector<string> m_expectedCode;
@ -150,6 +188,58 @@ void OverallTests::testIncludingGeneratedHeader()
CPPUNIT_ASSERT_EQUAL(test.yetAnotherString, parsedTest.yetAnotherString);
}
/*!
* \brief Like testIncludingGeneratedHeader() but also tests single inheritence.
*/
void OverallTests::testSingleInheritence()
{
DerivedTestStruct test;
test.someInt = 42;
test.someString = "the answer";
test.yetAnotherString = "but what was the question";
test.someBool = false;
const string expectedJSONForBase("{\"someInt\":42,\"someString\":\"the answer\",\"yetAnotherString\":\"but what was the question\"}");
const string expectedJSONForDerived(
"{\"someInt\":42,\"someString\":\"the answer\",\"yetAnotherString\":\"but what was the question\",\"someBool\":false}");
// test serialization
CPPUNIT_ASSERT_EQUAL(expectedJSONForBase, string(static_cast<const JSONSerializable<TestStruct> &>(test).toJson().GetString()));
CPPUNIT_ASSERT_EQUAL(expectedJSONForDerived, string(static_cast<const JSONSerializable<DerivedTestStruct> &>(test).toJson().GetString()));
// test deserialization
const DerivedTestStruct parsedTest(JSONSerializable<DerivedTestStruct>::fromJson(expectedJSONForDerived));
CPPUNIT_ASSERT_EQUAL(test.someInt, parsedTest.someInt);
CPPUNIT_ASSERT_EQUAL(test.someString, parsedTest.someString);
CPPUNIT_ASSERT_EQUAL(test.yetAnotherString, parsedTest.yetAnotherString);
CPPUNIT_ASSERT_EQUAL(test.someBool, parsedTest.someBool);
}
/*!
* \brief Like testIncludingGeneratedHeader() but also tests multiple inheritence.
*/
void OverallTests::testMultipleInheritence()
{
MultipleDerivedTestStruct test;
test.someInt = 42;
test.someString = "the answer";
test.yetAnotherString = "but what was the question";
test.someBool = false;
test.arrayOfStrings = { "array", "of", "strings" };
const string expectedJSONForDerived("{\"someInt\":42,\"someString\":\"the answer\",\"yetAnotherString\":\"but what was the "
"question\",\"arrayOfStrings\":[\"array\",\"of\",\"strings\"],\"someBool\":false}");
// test serialization
CPPUNIT_ASSERT_EQUAL(expectedJSONForDerived, string(static_cast<const JSONSerializable<MultipleDerivedTestStruct> &>(test).toJson().GetString()));
// test deserialization
const MultipleDerivedTestStruct parsedTest(JSONSerializable<MultipleDerivedTestStruct>::fromJson(expectedJSONForDerived));
CPPUNIT_ASSERT_EQUAL(test.someInt, parsedTest.someInt);
CPPUNIT_ASSERT_EQUAL(test.someString, parsedTest.someString);
CPPUNIT_ASSERT_EQUAL(test.yetAnotherString, parsedTest.yetAnotherString);
CPPUNIT_ASSERT_EQUAL(test.someBool, parsedTest.someBool);
CPPUNIT_ASSERT_EQUAL(test.arrayOfStrings, parsedTest.arrayOfStrings);
}
// include file required for reflection of TestStruct; generation of this header is triggered using
// the CMake function add_reflection_generator_invocation()
#include "reflection.h"