diff --git a/README.md b/README.md index 74382aa..5255923 100644 --- a/README.md +++ b/README.md @@ -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("..."); ``` -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 { + 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 diff --git a/generator/jsonserializationcodegenerator.cpp b/generator/jsonserializationcodegenerator.cpp index f397ea9..fcba484 100644 --- a/generator/jsonserializationcodegenerator.cpp +++ b/generator/jsonserializationcodegenerator.cpp @@ -3,6 +3,7 @@ #include "../lib/json/serializable.h" #include +#include #include #include @@ -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(decl); - if (templRecord->getQualifiedNameAsString() == JsonReflector::AdaptedJsonSerializable::qualifiedName) { + if (templRecord->getQualifiedNameAsString() == AdaptedJsonSerializable::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"; } } diff --git a/generator/tests/jsongenerator.cpp b/generator/tests/jsongenerator.cpp index 213bb92..ef4da96 100644 --- a/generator/tests/jsongenerator.cpp +++ b/generator/tests/jsongenerator.cpp @@ -220,12 +220,11 @@ void JsonGeneratorTests::testCustomSerialization() */ void JsonGeneratorTests::test3rdPartyAdaption() { - static_assert(ReflectiveRapidJSON::JsonReflector::AdaptedJsonSerializable::value, - "can serialize NotJsonSerializable because of adaption macro"); - static_assert(!ReflectiveRapidJSON::JsonReflector::AdaptedJsonSerializable::value, + static_assert( + ReflectiveRapidJSON::AdaptedJsonSerializable::value, "can serialize NotJsonSerializable because of adaption macro"); + static_assert(!ReflectiveRapidJSON::AdaptedJsonSerializable::value, "can not serialize OtherNotJsonSerializable because adaption macro missing"); - static_assert(!ReflectiveRapidJSON::JsonReflector::AdaptedJsonSerializable::value, - "can not serialize ReallyNotJsonSerializable"); + static_assert(!ReflectiveRapidJSON::AdaptedJsonSerializable::value, "can not serialize ReallyNotJsonSerializable"); const NotJsonSerializable test; const string str("{\"butSerializableAnyways\":\"useful to adapt 3rd party structs\"}"); diff --git a/generator/tests/structs.h b/generator/tests/structs.h index 8ebf683..80d8dfe 100644 --- a/generator/tests/structs.h +++ b/generator/tests/structs.h @@ -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 { + REFLECTIVE_RAPIDJSON_ENABLE_PRIVATE_MEMBERS(NestedTestStruct); + friend class JsonGeneratorTests; + list> nested; + +private: deque deq; }; diff --git a/lib/json/reflector.h b/lib/json/reflector.h index b904f71..1b7b574 100644 --- a/lib/json/reflector.h +++ b/lib/json/reflector.h @@ -24,26 +24,13 @@ namespace ReflectiveRapidJSON { template struct JsonSerializable; +template struct AdaptedJsonSerializable; /*! * \brief The JsonReflector namespace contains helper functions to ease the use of RapidJSON for automatic (de)serialization. */ namespace JsonReflector { -template struct AdaptedJsonSerializable : Traits::Bool { - 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 : Traits::Bool { \ - } - /*! * \brief Casts the specified \a size to the size type used by RapidJSON ensuring no overflow happens. */ diff --git a/lib/json/serializable.h b/lib/json/serializable.h index 4b16b23..71a7468 100644 --- a/lib/json/serializable.h +++ b/lib/json/serializable.h @@ -98,6 +98,48 @@ const JsonSerializable &as(const Type &serializable) return static_cast &>(serializable); } +/*! + * \brief The AdaptedJsonSerializable class allows considering 3rd party classes as serializable. + */ +template struct AdaptedJsonSerializable : Traits::Bool { + 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 : Traits::Bool { \ + } + +/*! + * \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( \ + 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 & reflectable, \ + const ::RAPIDJSON_NAMESPACE::GenericValue<::RAPIDJSON_NAMESPACE::UTF8>::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