Allow optionally (de)serializing private members

This commit is contained in:
Martchus 2017-11-09 01:03:34 +01:00
parent e29dcce40f
commit 0d74d915f8
6 changed files with 111 additions and 27 deletions

View File

@ -139,6 +139,7 @@ So beside the `BOOST_HANA_DEFINE_STRUCT` macro, the usage remains the same.
* No context information for errors like type-mismatch available
* Inherited members not considered
* Support for enums is unlikely
* Attempt to access private members can not be prevented
### Enable reflection for 3rd party classes/structs
It is obvious that the previously shown examples do not work for classes
@ -162,12 +163,34 @@ ReflectiveRapidJSON::JsonReflector::toJson(...).GetString();
ReflectiveRapidJSON::JsonReflector::fromJson<ThridPartyStruct>("...");
```
The code generator will emit the same code in the same way as `JsonSerializable` was
The code generator will emit the code in the same way as if `JsonSerializable` was
used.
### Further examples
Checkout the test cases for further examples. Relevant files are in
the directories `lib/tests` and `generator/tests`.
By the way, the functions in the `ReflectiveRapidJSON::JsonReflector` namespace can also
be used when inheriting from `JsonSerializable` (instead of the member functions).
### (De)serializing private members
By default, private members are not considered for (de)serialization. However, it is possible
to enable this by adding `friend` methods for the helper functions of Reflective RapidJSON.
To make things easier, there's a macro provided:
```
struct SomeStruct : public JsonSerializable<SomeStruct> {
REFLECTIVE_RAPIDJSON_ENABLE_PRIVATE_MEMBERS(SomeStruct);
public:
std::string publicMember = "will be (de)serialized anyways";
private:
std::string privateMember = "will be (de)serialized with the help of REFLECTIVE_RAPIDJSON_ENABLE_PRIVATE_MEMBERS macro";
};
```
#### Caveats
* It will obviously not work for 3rd party structs.
* This way to allow (de)serialization of private members must be applied when using Boost.Hana
and there are any private members present. The reason is that accessing the private members can
currently not prevented when using Boost.Hana.
### Custom (de)serialization
Sometimes it is appropriate to implement custom (de)serialization. For instance, a
@ -178,6 +201,10 @@ An example for such custom (de)serialization can be found in the file
`json/reflector-chronoutilities.h`. It provides (de)serialization of `DateTime` and
`TimeSpan` objects from the C++ utilities library.
### Further examples
Checkout the test cases for further examples. Relevant files are in
the directories `lib/tests` and `generator/tests`.
## Install instructions
### Dependencies

View File

@ -3,6 +3,7 @@
#include "../lib/json/serializable.h"
#include <clang/AST/DeclCXX.h>
#include <clang/AST/DeclFriend.h>
#include <clang/AST/DeclTemplate.h>
#include <iostream>
@ -43,7 +44,7 @@ void JsonSerializationCodeGenerator::addDeclaration(clang::Decl *decl)
// check for template specializations to adapt a 3rd party class/struct
if (decl->getKind() == clang::Decl::Kind::ClassTemplateSpecialization) {
auto *const templRecord = static_cast<clang::ClassTemplateSpecializationDecl *>(decl);
if (templRecord->getQualifiedNameAsString() == JsonReflector::AdaptedJsonSerializable<void>::qualifiedName) {
if (templRecord->getQualifiedNameAsString() == AdaptedJsonSerializable<void>::qualifiedName) {
const clang::TemplateArgumentList &templateArgs = templRecord->getTemplateArgs();
if (templateArgs.size() != 1 || templateArgs.get(0).getKind() != clang::TemplateArgument::Type) {
return; // FIXME: use Clang diagnostics to print warning
@ -59,7 +60,7 @@ void JsonSerializationCodeGenerator::addDeclaration(clang::Decl *decl)
// add any other records
m_records.emplace_back(record);
}
} break;
case clang::Decl::Kind::Enum:
// TODO: add enums
break;
@ -88,6 +89,27 @@ 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 : relevantClasses) {
bool pushPrivateMembers = false;
bool pullPrivateMembers = false;
for (const clang::FriendDecl *const friendDecl : relevantClass.record->friends()) {
const clang::NamedDecl *const actualFriendDecl = friendDecl->getFriendDecl();
if (!actualFriendDecl /* && decl->getKind() != clang::Decl::Kind::FunctionTemplate */) {
continue;
}
const string friendName(actualFriendDecl->getQualifiedNameAsString());
if (friendName == "ReflectiveRapidJSON::JsonReflector::push") {
pushPrivateMembers = true;
}
if (friendName == "ReflectiveRapidJSON::JsonReflector::pull") {
pullPrivateMembers = true;
}
cout << "friend-kind: " << actualFriendDecl->getDeclKindName() << endl;
cout << "friend: " << actualFriendDecl->getQualifiedNameAsString() << endl;
if (pushPrivateMembers && pullPrivateMembers) {
break;
}
}
// write comment
os << "// define code for (de)serializing " << relevantClass.qualifiedName << " objects\n";
@ -103,7 +125,7 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
}
os << " // push members\n";
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
if (field->getAccess() == clang::AS_public) {
if (pushPrivateMembers || field->getAccess() == clang::AS_public) {
os << " push(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, allocator);\n";
}
}
@ -128,7 +150,7 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
" }\n"
" // pull members\n";
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
if (field->getAccess() == clang::AS_public) {
if (pullPrivateMembers || field->getAccess() == clang::AS_public) {
os << " pull(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, errors);\n";
}
}

View File

@ -220,12 +220,11 @@ void JsonGeneratorTests::testCustomSerialization()
*/
void JsonGeneratorTests::test3rdPartyAdaption()
{
static_assert(ReflectiveRapidJSON::JsonReflector::AdaptedJsonSerializable<NotJsonSerializable>::value,
"can serialize NotJsonSerializable because of adaption macro");
static_assert(!ReflectiveRapidJSON::JsonReflector::AdaptedJsonSerializable<OtherNotJsonSerializable>::value,
static_assert(
ReflectiveRapidJSON::AdaptedJsonSerializable<NotJsonSerializable>::value, "can serialize NotJsonSerializable because of adaption macro");
static_assert(!ReflectiveRapidJSON::AdaptedJsonSerializable<OtherNotJsonSerializable>::value,
"can not serialize OtherNotJsonSerializable because adaption macro missing");
static_assert(!ReflectiveRapidJSON::JsonReflector::AdaptedJsonSerializable<ReallyNotJsonSerializable>::value,
"can not serialize ReallyNotJsonSerializable");
static_assert(!ReflectiveRapidJSON::AdaptedJsonSerializable<ReallyNotJsonSerializable>::value, "can not serialize ReallyNotJsonSerializable");
const NotJsonSerializable test;
const string str("{\"butSerializableAnyways\":\"useful to adapt 3rd party structs\"}");

View File

@ -28,12 +28,19 @@ private:
string privateString = "not going to be serialized";
};
class JsonGeneratorTests;
/*!
* \brief The NestedTestStruct struct inherits from JsonSerializable and should hence have functional fromJson()
* and toJson() methods. This is asserted in JsonGeneratorTests::testNesting();
*/
struct NestedTestStruct : public JsonSerializable<NestedTestStruct> {
REFLECTIVE_RAPIDJSON_ENABLE_PRIVATE_MEMBERS(NestedTestStruct);
friend class JsonGeneratorTests;
list<vector<TestStruct>> nested;
private:
deque<double> deq;
};

View File

@ -24,26 +24,13 @@
namespace ReflectiveRapidJSON {
template <typename Type> struct JsonSerializable;
template <typename Type> struct AdaptedJsonSerializable;
/*!
* \brief The JsonReflector namespace contains helper functions to ease the use of RapidJSON for automatic (de)serialization.
*/
namespace JsonReflector {
template <typename T> struct AdaptedJsonSerializable : Traits::Bool<false> {
static constexpr const char *name = "AdaptedJsonSerializable";
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::JsonReflector::AdaptedJsonSerializable";
};
/*!
* \def The REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE macro allows to adapt (de)serialization for types defined in 3rd party header files.
* \remarks The struct will not have the toJson() and fromJson() methods available. Use the corresponding functions in the namespace
* ReflectiveRapidJSON::JsonReflector instead.
*/
#define REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(T) \
template <> struct ::ReflectiveRapidJSON::JsonReflector::AdaptedJsonSerializable<T> : Traits::Bool<true> { \
}
/*!
* \brief Casts the specified \a size to the size type used by RapidJSON ensuring no overflow happens.
*/

View File

@ -98,6 +98,48 @@ const JsonSerializable<Type> &as(const Type &serializable)
return static_cast<const JsonSerializable<Type> &>(serializable);
}
/*!
* \brief The AdaptedJsonSerializable class allows considering 3rd party classes as serializable.
*/
template <typename T> struct AdaptedJsonSerializable : Traits::Bool<false> {
static constexpr const char *name = "AdaptedJsonSerializable";
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::AdaptedJsonSerializable";
};
/*!
* \def The REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE macro allows to adapt (de)serialization for types defined in 3rd party header files.
* \remarks The struct will not have the toJson() and fromJson() methods available. Use the corresponding functions in the namespace
* ReflectiveRapidJSON::JsonReflector instead.
*/
#define REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(T) \
template <> struct ::ReflectiveRapidJSON::AdaptedJsonSerializable<T> : Traits::Bool<true> { \
}
/*!
* \def The REFLECTIVE_RAPIDJSON_PUSH_PRIVATE_MEMBERS macro enables serialization of private members.
* \remarks For an example, see README.md.
*/
#define REFLECTIVE_RAPIDJSON_PUSH_PRIVATE_MEMBERS(T) \
friend void ::ReflectiveRapidJSON::JsonReflector::push<T>( \
const T &reflectable, ::RAPIDJSON_NAMESPACE::Value::Object &value, ::RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
/*!
* \def The REFLECTIVE_RAPIDJSON_PULL_PRIVATE_MEMBERS macro enables deserialization of private members.
* \remarks For an example, see README.md.
*/
#define REFLECTIVE_RAPIDJSON_PULL_PRIVATE_MEMBERS(T) \
friend void ::ReflectiveRapidJSON::JsonReflector::pull<T>(T & reflectable, \
const ::RAPIDJSON_NAMESPACE::GenericValue<::RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value, \
::ReflectiveRapidJSON::JsonDeserializationErrors *errors)
/*!
* \def The REFLECTIVE_RAPIDJSON_ENABLE_PRIVATE_MEMBERS macro enables serialization and deserialization of private members.
* \remarks For an example, see README.md.
*/
#define REFLECTIVE_RAPIDJSON_ENABLE_PRIVATE_MEMBERS(T) \
REFLECTIVE_RAPIDJSON_PUSH_PRIVATE_MEMBERS(T); \
REFLECTIVE_RAPIDJSON_PULL_PRIVATE_MEMBERS(T)
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_JSON_SERIALIZABLE_H