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 * No context information for errors like type-mismatch available
* Inherited members not considered * Inherited members not considered
* Support for enums is unlikely * Support for enums is unlikely
* Attempt to access private members can not be prevented
### Enable reflection for 3rd party classes/structs ### Enable reflection for 3rd party classes/structs
It is obvious that the previously shown examples do not work for classes 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>("..."); 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. used.
### Further examples By the way, the functions in the `ReflectiveRapidJSON::JsonReflector` namespace can also
Checkout the test cases for further examples. Relevant files are in be used when inheriting from `JsonSerializable` (instead of the member functions).
the directories `lib/tests` and `generator/tests`.
### (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 ### Custom (de)serialization
Sometimes it is appropriate to implement custom (de)serialization. For instance, a 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 `json/reflector-chronoutilities.h`. It provides (de)serialization of `DateTime` and
`TimeSpan` objects from the C++ utilities library. `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 ## Install instructions
### Dependencies ### Dependencies

View File

@ -3,6 +3,7 @@
#include "../lib/json/serializable.h" #include "../lib/json/serializable.h"
#include <clang/AST/DeclCXX.h> #include <clang/AST/DeclCXX.h>
#include <clang/AST/DeclFriend.h>
#include <clang/AST/DeclTemplate.h> #include <clang/AST/DeclTemplate.h>
#include <iostream> #include <iostream>
@ -43,7 +44,7 @@ void JsonSerializationCodeGenerator::addDeclaration(clang::Decl *decl)
// check for template specializations to adapt a 3rd party class/struct // check for template specializations to adapt a 3rd party class/struct
if (decl->getKind() == clang::Decl::Kind::ClassTemplateSpecialization) { if (decl->getKind() == clang::Decl::Kind::ClassTemplateSpecialization) {
auto *const templRecord = static_cast<clang::ClassTemplateSpecializationDecl *>(decl); 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(); const clang::TemplateArgumentList &templateArgs = templRecord->getTemplateArgs();
if (templateArgs.size() != 1 || templateArgs.get(0).getKind() != clang::TemplateArgument::Type) { if (templateArgs.size() != 1 || templateArgs.get(0).getKind() != clang::TemplateArgument::Type) {
return; // FIXME: use Clang diagnostics to print warning return; // FIXME: use Clang diagnostics to print warning
@ -59,7 +60,7 @@ void JsonSerializationCodeGenerator::addDeclaration(clang::Decl *decl)
// add any other records // add any other records
m_records.emplace_back(record); m_records.emplace_back(record);
} } break;
case clang::Decl::Kind::Enum: case clang::Decl::Kind::Enum:
// TODO: add enums // TODO: add enums
break; 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 // 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...") // output, see ../lib/tests/jsonserializable.cpp (code under comment "pretend serialization code...")
for (const RelevantClass &relevantClass : relevantClasses) { 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 // write comment
os << "// define code for (de)serializing " << relevantClass.qualifiedName << " objects\n"; os << "// define code for (de)serializing " << relevantClass.qualifiedName << " objects\n";
@ -103,7 +125,7 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
} }
os << " // push members\n"; os << " // push members\n";
for (const clang::FieldDecl *field : relevantClass.record->fields()) { 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"; os << " push(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, allocator);\n";
} }
} }
@ -128,7 +150,7 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
" }\n" " }\n"
" // pull members\n"; " // pull members\n";
for (const clang::FieldDecl *field : relevantClass.record->fields()) { 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"; os << " pull(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, errors);\n";
} }
} }

View File

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

View File

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

View File

@ -24,26 +24,13 @@
namespace ReflectiveRapidJSON { namespace ReflectiveRapidJSON {
template <typename Type> struct JsonSerializable; 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. * \brief The JsonReflector namespace contains helper functions to ease the use of RapidJSON for automatic (de)serialization.
*/ */
namespace JsonReflector { 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. * \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); 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 } // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_JSON_SERIALIZABLE_H #endif // REFLECTIVE_RAPIDJSON_JSON_SERIALIZABLE_H