Compare commits

...

32 Commits

Author SHA1 Message Date
Martchus cc1641e0f8 Avoid use of deprecated `exec_program` 2023-12-19 20:28:41 +01:00
Martchus 1ea5b1e744 Avoid CMake deprecation warning by bumping version 2023-07-23 21:20:51 +02:00
Martchus 7c8ef68155 Avoid including headers from host when cross compiling 2023-06-06 16:31:06 +02:00
Martchus e3e4596481 Allow configuring error resilience via CMake helper 2023-05-28 20:59:08 +02:00
Martchus 091d521152 Use `pubsetbuf` only with `libstdc++`
This usage of the function seems only to work as intended with that
standard lib. With `libc++` and the MSVC standard lib the call has no
effect.
2023-02-28 21:19:40 +01:00
Martchus 9a6d550d8f Apply clang-format/cmake-format 2023-02-03 13:34:19 +01:00
Martchus e82d834bf2 Update copyright notice 2023-01-17 18:36:21 +01:00
Martchus 27b029ba67 Avoid invalid `-std=c++…` flag when `CXX_STANDARD` is empty in CMake macro 2022-12-23 20:54:54 +01:00
Martchus d50a4c6004 Consider the `COMPILE_OPTIONS` target property as well in CMake macro 2022-12-23 20:53:22 +01:00
Martchus 10bb97bbc4 Apply clang-format 2022-10-13 19:59:20 +02:00
Martchus b1bd782910 Fix typo in README 2022-10-13 19:58:40 +02:00
Martchus 2b0048f144 Change (de)serialization order of versioning and base classes
This allows deserializing the first base class on its own, even then the
derived class has been serialized. That makes sense if the full object (of
the derived class) should be stored but sometimes only the "base fields"
(of the base class) are needed.
2022-05-28 18:46:21 +02:00
Martchus 5c7a6cba8c Add default operator== to serializable classes
Otherwise derived classes cannot use add a default themselves. Maybe this
makes sense for more comparision operators. Note that I'm still testing the
usefulness. It already breaks when using multiple inheritane so maybe there
is a better way.

The operator is guarded by a version constraint because it is a C++20
feature.
2022-05-15 21:03:20 +02:00
Martchus 60d761f7ed Pass version down to base classes in binary deserializer
So the behavior is consistent with readng members and with serialization.

It should be fine because if the base class is versioned it won't make a
difference but if it is versioned the version from the derived class can be
utilized.
2022-05-15 21:00:02 +02:00
Martchus 9c3bf01c8f Avoid warnings about unused var `allocator` for empty classes 2022-05-15 20:52:30 +02:00
Martchus 1e3417f8d0 Fix (de)serialization of std::int8_t/std::uint8_t
Those types are not supported by RapidJSON and therefore need extra
handling.
2022-05-15 03:11:39 +02:00
Martchus a6b9d771aa Do not treat warning "'this' pointer is null" appearing with GCC 12 as error
It is about code included from libclang and likely wrong so let's just not
abort the compilation due to it.
2022-05-12 20:23:33 +02:00
Martchus 762540f5e5 Add support for `std::optional` 2022-05-07 18:40:37 +02:00
Martchus 0633923935 Simplify use of type traits in `json/reflector.h` 2022-05-07 16:52:32 +02:00
Martchus 11491b1387 Add stalebot config 2022-04-12 01:05:03 +02:00
Martchus df787f3105 Add copyright notice 2022-04-05 20:23:32 +02:00
Martchus 2cc044c705 Adapt tests to efaa8a8 2022-03-28 17:06:28 +02:00
Martchus 4966625d8b Clarify that license is "GPL-2-or-later" 2022-03-15 21:46:56 +01:00
Martchus efaa8a8441 Avoid warnings about using uninitialized variable 2022-02-22 20:03:49 +01:00
Martchus 22611457f9 Fix plural of pointer in README.md 2022-02-05 22:28:20 +01:00
Martchus 59ff3c19eb Allow reporting a type mismatch specifying the expected RapidJSON type directly 2022-01-03 23:24:40 +01:00
Martchus f8f551a78a Fix include and namespace-prefix for using `std::move` in JSON reflector 2021-12-05 23:34:39 +01:00
Martchus 8b66ca3e6b Avoid using `DeclBase::getLangOpts()` to support older Clang versions
The `DeclBase::getLangOpts()` is merely a convenience but only available
in Clang 11 or newer.
2021-09-19 01:17:30 +02:00
Martchus 44c6b8c609 Throw exception during binary deserialization when version is not supported 2021-07-25 19:19:02 +02:00
Martchus 13428667f8 Remove surplus space 2021-07-25 19:13:29 +02:00
Martchus 852dfb7e3c Add symlink for versioning 2021-07-25 17:31:07 +02:00
Martchus 0a902ac30c Add experimental versioning for binary (de)serializer 2021-07-13 00:37:05 +02:00
30 changed files with 808 additions and 197 deletions

19
.github/stale.yml vendored Normal file
View File

@ -0,0 +1,19 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- feature request
- enhancement
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# set metadata
project(reflective_rapidjson)

View File

@ -62,18 +62,19 @@ The following table shows the mapping of supported C++ types to supported JSON t
| iteratable lists (`std::vector`, `std::list`, ...) | array |
| sets (`std::set`, `std::unordered_set`, `std::multiset`, ...) | array |
| `std::pair`, `std::tuple` | array |
| `std::unique_ptr`, `std::shared_ptr` | depends/null |
| `std::unique_ptr`, `std::shared_ptr`, `std::optional` | depends/null |
| `std::map`, `std::unordered_map`, `std::multimap`, `std::unordered_multimap` | object |
| `std::variant` | object |
| `JsonSerializable` | object |
### Remarks
* Raw pointer are not supported. This prevents
* Raw pointers are not supported. This prevents
forgetting to free memory which would have to be allocated when deserializing.
* For the same reason `const char *` and `std::string_view` are only supported for serialization.
* Enums are (de)serialized as their underlying integer value. When deserializing, it is currently *not* checked
whether the present integer value is a valid enumeration item.
* The JSON type for smart pointer depends on the type the pointer refers to. It can also be `null`.
* The JSON type for smart pointers and `std::optional` depends on the type the pointer/optional refers to.
It can also be `null` for null pointers or `std::optional` without value.
* If multiple `std::shared_ptr` instances point to the same object this object is serialized multiple times.
When deserializing those identical objects, it is currently not possible to share the memory (again). So each
`std::shared_ptr` will point to its own copy. Note that this limitation is *not* present when using binary
@ -353,6 +354,54 @@ 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 mentioned under dependencies.
### Versioning
#### JSON (de)serializer
The JSON (de)serializer doesn't support versioning at this point. It'll simply read/write the
members present in the struct. Additional members (which were e.g. present in older/newer
versions of the struct) are ignored when reading and in consequence dropped when writing.
#### Binary (de)serializer
The binary (de)serializer supports *very* experimental versioning. Otherwise adding/removing
members is a breaking change. The versioning looks like this:
<pre>
// enable definition of the macros shown below (otherwise use long macros defined in
// `lib/versioning.h`)
#define REFLECTIVE_RAPIDJSON_SHORT_MACROS
#include &lt;reflective_rapidjson/binary/serializable.h&gt;
// example struct where version is *not* serialized/deserialized; defaults to version from
// outer scope when reading/writing, defaults to version 0 on top-level
struct Nested : public BinarySerializable&lt;Nested&gt; { //
std::uint32_t foo; // will be read/written in any case
as_of_version(3):
std::uint32_t bar; // will be read/written if outer scope version is &gt;= 3
};
// example struct where version is serialized/deserialized; defaults to version 3 when writing
struct Example : public BinarySerializable&lt;Example, 3&gt; {
Nested nested; // will be read/written in any case, version is "propagated down"
std::uint32_t a, b; // will be read/written in any case
until_version(2):
std::uint32_t c, d; // will be read/written if version is &lt;= 2
as_of_version(3):
std::uint32_t e, f; // will be read/written if version is &gt;= 3
as_of_version(4):
std::uint32_t g; // will be read/written if version is &gt;= 4
};
</pre>
The version specified as template argument is also assumed to be the highest supported version.
If a higher version is encountered during deserialization, `BinaryVersionNotSupported` is thrown
and the deserialization aborted.
Note that the versioning is mostly untested at this point.
### Remarks
* Static member variables and member functions are currently ignored by the generator.
* It is currently not possible to ignore a specific member variable.
@ -462,7 +511,7 @@ make
make check
# build tests but do not run them (optional, requires CppUnit)
make tests
# generate API documentation (optional, reqquires Doxygen)
# generate API documentation (optional, requires Doxygen)
make apidoc
# install header files, libraries and generator
make install DESTDIR="/temporary/install/location"
@ -485,3 +534,8 @@ Add eg. `-j$(nproc)` to `make` arguments for using all cores.
These packages shows the required dependencies and commands to build in a plain way. So they might be useful for
making Reflective RapidJSON available under other platforms, too.
## Copyright notice and license
Copyright © 2017-2023 Marius Kittler
All code is licensed under [GPL-2-or-later](LICENSE).

View File

@ -29,5 +29,5 @@
- [x] Support `std::unique_ptr` and `std::shared_ptr`
- [x] Support `std::map` and `std::unordered_map`
- [ ] Support `std::any`
- [ ] Support `std::optional`
- [x] Support `std::optional`
- [x] Support/document customized (de)serialization (eg. serialize some `DateTime` object to ISO string representation)

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# metadata
set(META_PROJECT_NAME reflective_rapidjson_generator)
@ -60,6 +60,9 @@ endif ()
# also add reflective_rapidjson which is header-only but might pull additional include dirs for RapidJSON
list(APPEND PRIVATE_LIBRARIES "${REFLECTIVE_RAPIDJSON_TARGET_NAME}")
# avoid warning "'this' pointer is null" from GCC 12 about code included from libclang
list(APPEND META_PRIVATE_COMPILE_OPTIONS "-Wno-error=nonnull")
# include modules to apply configuration
include(BasicConfig)
include(WindowsResources)

View File

@ -5,6 +5,10 @@
#include <clang/AST/DeclCXX.h>
#include <clang/AST/DeclFriend.h>
#include <clang/AST/DeclTemplate.h>
#include <clang/AST/Expr.h>
#include <clang/AST/RecursiveASTVisitor.h>
#include <llvm/ADT/APInt.h>
#include <iostream>
@ -35,36 +39,159 @@ BinarySerializationCodeGenerator::BinarySerializationCodeGenerator(CodeFactory &
}
/*!
* \brief Returns the qualified name of the specified \a record if it is considered relevant.
* \brief Checks whether \a possiblyRelevantClass is actually relevant.
*/
string BinarySerializationCodeGenerator::qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const
void BinarySerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
{
const string qualifiedName(record->getQualifiedNameAsString());
switch (isQualifiedNameIfRelevant(record, qualifiedName)) {
case IsRelevant::Yes:
return qualifiedName;
case IsRelevant::No:
return string();
default:;
SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
return;
}
// consider all classes specified via "--additional-classes" argument relevant
if (!m_options.additionalClassesArg.isPresent()) {
return string();
return;
}
for (const char *className : m_options.additionalClassesArg.values()) {
if (className == qualifiedName) {
return qualifiedName;
for (const char *const className : m_options.additionalClassesArg.values()) {
if (className == possiblyRelevantClass.qualifiedName) {
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
return;
}
}
}
return string();
/// \brief The RetrieveIntegerLiteralFromDeclaratorDecl struct is used to traverse a variable declaration to get the integer value.
struct RetrieveIntegerLiteralFromDeclaratorDecl : public clang::RecursiveASTVisitor<RetrieveIntegerLiteralFromDeclaratorDecl> {
explicit RetrieveIntegerLiteralFromDeclaratorDecl(const clang::ASTContext &ctx);
bool VisitStmt(clang::Stmt *st);
const clang::ASTContext &ctx;
std::uint64_t res;
bool success;
};
/// \brief Constructs a new instance for the specified AST context.
RetrieveIntegerLiteralFromDeclaratorDecl::RetrieveIntegerLiteralFromDeclaratorDecl(const clang::ASTContext &ctx)
: ctx(ctx)
, res(0)
, success(false)
{
}
/// \brief Reads the integer value of \a st for integer literals.
bool RetrieveIntegerLiteralFromDeclaratorDecl::VisitStmt(clang::Stmt *st)
{
if (st->getStmtClass() != clang::Stmt::IntegerLiteralClass) {
return true;
}
const auto *const integerLiteral = static_cast<const clang::IntegerLiteral *>(st);
auto evaluation = clang::Expr::EvalResult();
integerLiteral->EvaluateAsInt(evaluation, ctx, clang::Expr::SE_NoSideEffects, true);
if (!evaluation.Val.isInt()) {
return true;
}
const auto &asInt = evaluation.Val.getInt();
if (asInt.getActiveBits() > 64) {
return true;
}
res = asInt.getZExtValue();
success = true;
return false;
}
/// \brief The MemberTracking struct is an internal helper for BinarySerializationCodeGenerator::generate().
struct MemberTracking {
bool membersWritten = false, withinCondition = false;
BinaryVersion asOfVersion = BinaryVersion(), lastAsOfVersion = BinaryVersion();
BinaryVersion untilVersion = BinaryVersion(), lastUntilVersion = BinaryVersion();
bool checkForVersionMarker(clang::Decl *decl);
void concludeCondition(std::ostream &os);
void writeVersionCondition(std::ostream &os);
void writeExtraPadding(std::ostream &os);
};
/*!
* \brief Returns whether \a delc is a static member variable and processes special static member variables
* for versioning.
*/
bool MemberTracking::checkForVersionMarker(clang::Decl *decl)
{
if (decl->getKind() != clang::Decl::Kind::Var) {
return false;
}
auto *const declarator = static_cast<clang::DeclaratorDecl *>(decl);
const auto declarationName = declarator->getName();
const auto isAsOfVersion = declarationName.startswith("rrjAsOfVersion");
if (isAsOfVersion || declarationName.startswith("rrjUntilVersion")) {
auto v = RetrieveIntegerLiteralFromDeclaratorDecl(declarator->getASTContext());
v.TraverseDecl(declarator);
if (v.success) {
if (isAsOfVersion) {
asOfVersion = v.res;
if (asOfVersion > untilVersion) {
untilVersion = 0;
}
} else {
untilVersion = v.res;
if (untilVersion < asOfVersion) {
asOfVersion = 0;
}
}
}
}
return true;
}
/*!
* \brief Concludes an unfinished version condition if-block.
*/
void MemberTracking::concludeCondition(std::ostream &os)
{
if (withinCondition) {
os << " }\n";
}
}
/*!
* \brief Starts a new version condition if-block if versioning parameters have changed.
*/
void MemberTracking::writeVersionCondition(std::ostream &os)
{
if (asOfVersion == lastAsOfVersion && untilVersion == lastUntilVersion) {
return;
}
concludeCondition(os);
lastAsOfVersion = asOfVersion;
lastUntilVersion = untilVersion;
if ((withinCondition = asOfVersion || untilVersion)) {
os << " if (";
if (asOfVersion) {
os << "version >= " << asOfVersion;
if (untilVersion) {
os << " && ";
}
}
if (untilVersion) {
os << "version <= " << untilVersion;
}
os << ") {\n";
}
}
/*!
* \brief Writes extra padding (if within a version condition).
*/
void MemberTracking::writeExtraPadding(std::ostream &os)
{
if (withinCondition) {
os << " ";
}
}
/*!
* \brief Generates pull() and push() helper functions in the ReflectiveRapidJSON::BinaryReflector namespace for the relevant classes.
*/
void BinarySerializationCodeGenerator::generate(ostream &os) const
void BinarySerializationCodeGenerator::generate(std::ostream &os) const
{
// initialize source manager to make use of isOnlyIncluded() for skipping records which are only included
lazyInitializeSourceManager();
@ -118,22 +245,55 @@ void BinarySerializationCodeGenerator::generate(ostream &os) const
// print writeCustomType method
os << "template <> " << visibility << " void writeCustomType<::" << relevantClass.qualifiedName
<< ">(BinarySerializer &serializer, const ::" << relevantClass.qualifiedName
<< " &customObject)\n{\n"
" // write base classes\n";
<< ">(BinarySerializer &serializer, const ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
os << " // write base classes\n";
for (const RelevantClass *baseClass : relevantBases) {
os << " serializer.write(static_cast<const ::" << baseClass->qualifiedName << " &>(customObject));\n";
os << " serializer.write(static_cast<const ::" << baseClass->qualifiedName << " &>(customObject), version);\n";
}
if (!relevantClass.relevantBase.empty()) {
os << " // write version\n"
" using V = Versioning<"
<< relevantClass.relevantBase
<< ">;\n"
" if constexpr (V::enabled) {\n"
" serializer.writeVariableLengthUIntBE(V::applyDefault(version));\n"
" }\n";
}
os << " // write members\n";
auto membersWritten = false;
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
if (writePrivateMembers || field->getAccess() == clang::AS_public) {
os << " serializer.write(customObject." << field->getName() << ");\n";
membersWritten = true;
auto mt = MemberTracking();
for (clang::Decl *const decl : relevantClass.record->decls()) {
// check static member variables for version markers
if (mt.checkForVersionMarker(decl)) {
continue;
}
// skip all further declarations but fields
if (decl->getKind() != clang::Decl::Kind::Field) {
continue;
}
// skip const members
const auto *const field = static_cast<const clang::FieldDecl *>(decl);
if (field->getType().isConstant(field->getASTContext())) {
continue;
}
// skip private members conditionally
if (!writePrivateMembers && field->getAccess() != clang::AS_public) {
continue;
}
// write version markers
mt.writeVersionCondition(os);
mt.writeExtraPadding(os);
// write actual code for serialization
os << " serializer.write(customObject." << field->getName() << ", version);\n";
mt.membersWritten = true;
}
if (relevantBases.empty() && !membersWritten) {
os << " (void)serializer;\n (void)customObject;\n";
mt.concludeCondition(os);
if (relevantBases.empty() && !mt.membersWritten) {
os << " (void)serializer;\n (void)customObject;\n \n(void)version;";
}
os << "}\n";
@ -143,28 +303,56 @@ void BinarySerializationCodeGenerator::generate(ostream &os) const
}
// print readCustomType method
os << "template <> " << visibility << " void readCustomType<::" << relevantClass.qualifiedName
<< ">(BinaryDeserializer &deserializer, ::" << relevantClass.qualifiedName
<< " &customObject)\n{\n"
" // read base classes\n";
mt = MemberTracking();
os << "template <> " << visibility << " BinaryVersion readCustomType<::" << relevantClass.qualifiedName
<< ">(BinaryDeserializer &deserializer, ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
os << " // read base classes\n";
for (const RelevantClass *baseClass : relevantBases) {
os << " deserializer.read(static_cast<::" << baseClass->qualifiedName << " &>(customObject));\n";
os << " deserializer.read(static_cast<::" << baseClass->qualifiedName << " &>(customObject), version);\n";
}
if (!relevantClass.relevantBase.empty()) {
os << " // read version\n"
" using V = Versioning<"
<< relevantClass.relevantBase
<< ">;\n"
" if constexpr (V::enabled) {\n"
" V::assertVersion(version = deserializer.readVariableLengthUIntBE(), \""
<< relevantClass.qualifiedName
<< "\");\n"
" }\n";
}
os << " // read members\n";
auto membersRead = false;
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
for (clang::Decl *const decl : relevantClass.record->decls()) {
// check static member variables for version markers
if (mt.checkForVersionMarker(decl)) {
continue;
}
// skip all further declarations but fields
if (decl->getKind() != clang::Decl::Kind::Field) {
continue;
}
// skip const members
const auto *const field = static_cast<const clang::FieldDecl *>(decl);
if (field->getType().isConstant(field->getASTContext())) {
continue;
}
// write version markers
mt.writeVersionCondition(os);
mt.writeExtraPadding(os);
if (readPrivateMembers || field->getAccess() == clang::AS_public) {
os << " deserializer.read(customObject." << field->getName() << ");\n";
membersRead = true;
os << " deserializer.read(customObject." << field->getName() << ", version);\n";
mt.membersWritten = true;
}
}
if (relevantBases.empty() && !membersRead) {
mt.concludeCondition(os);
if (relevantBases.empty() && !mt.membersWritten) {
os << " (void)deserializer;\n (void)customObject;\n";
}
os << " return version;\n";
os << "}\n\n";
}

View File

@ -27,7 +27,7 @@ public:
void generate(std::ostream &os) const override;
protected:
std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const override;
void computeRelevantClass(RelevantClass &possiblyRelevantClass) const override;
const Options &m_options;
};

View File

@ -44,17 +44,18 @@ bool CodeGenerator::isOnlyIncluded(const clang::Decl *declaration) const
/*!
* \brief Returns whether the specified \a record inherits from an instantiation of the specified \a templateClass.
* \returns Returns the relevant base class if that's the case and otherwise nullptr.
* \remarks The specified \a record must be defined (not only forward-declared).
*/
bool CodeGenerator::inheritsFromInstantiationOf(clang::CXXRecordDecl *const record, const char *const templateClass)
clang::CXXBaseSpecifier *CodeGenerator::inheritsFromInstantiationOf(clang::CXXRecordDecl *const record, const char *const templateClass)
{
for (const clang::CXXBaseSpecifier &base : record->bases()) {
const clang::CXXRecordDecl *const baseDecl = base.getType()->getAsCXXRecordDecl();
for (clang::CXXBaseSpecifier &base : record->bases()) {
clang::CXXRecordDecl *const baseDecl = base.getType()->getAsCXXRecordDecl();
if (baseDecl && baseDecl->getQualifiedNameAsString() == templateClass) {
return true;
return &base;
}
}
return false;
return nullptr;
}
} // namespace ReflectiveRapidJSON

View File

@ -8,6 +8,7 @@
namespace clang {
class Decl;
class CXXRecordDecl;
class CXXBaseSpecifier;
class SourceManager;
} // namespace clang
@ -32,7 +33,7 @@ protected:
CodeFactory &factory() const;
void lazyInitializeSourceManager() const;
bool isOnlyIncluded(const clang::Decl *declaration) const;
static bool inheritsFromInstantiationOf(clang::CXXRecordDecl *record, const char *templateClass);
static clang::CXXBaseSpecifier *inheritsFromInstantiationOf(clang::CXXRecordDecl *record, const char *templateClass);
private:
CodeFactory &m_factory;

View File

@ -35,30 +35,25 @@ JsonSerializationCodeGenerator::JsonSerializationCodeGenerator(CodeFactory &fact
}
/*!
* \brief Returns the qualified name of the specified \a record if it is considered relevant.
* \brief Checks whether \a possiblyRelevantClass is actually relevant.
*/
string JsonSerializationCodeGenerator::qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const
void JsonSerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
{
const string qualifiedName(record->getQualifiedNameAsString());
switch (isQualifiedNameIfRelevant(record, qualifiedName)) {
case IsRelevant::Yes:
return qualifiedName;
case IsRelevant::No:
return string();
default:;
SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
return;
}
// consider all classes specified via "--additional-classes" argument relevant
if (!m_options.additionalClassesArg.isPresent()) {
return string();
return;
}
for (const char *className : m_options.additionalClassesArg.values()) {
if (className == qualifiedName) {
return qualifiedName;
for (const char *const className : m_options.additionalClassesArg.values()) {
if (className == possiblyRelevantClass.qualifiedName) {
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
return;
}
}
return string();
}
/*!
@ -132,7 +127,7 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
}
}
if (relevantBases.empty() && !pushWritten) {
os << " (void)reflectable;\n (void)value;\n";
os << " (void)reflectable;\n (void)value;\n (void)allocator;\n";
}
os << "}\n";
@ -151,7 +146,7 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
os << " pull(static_cast<::" << baseClass->qualifiedName << " &>(reflectable), value, errors);\n";
}
os << " // set error context for current record\n"
" const char *previousRecord;\n"
" const char *previousRecord = nullptr;\n"
" if (errors) {\n"
" previousRecord = errors->currentRecord;\n"
" errors->currentRecord = \""

View File

@ -27,7 +27,7 @@ public:
void generate(std::ostream &os) const override;
protected:
std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const override;
void computeRelevantClass(RelevantClass &possiblyRelevantClass) const override;
const Options &m_options;
};

View File

@ -5,6 +5,8 @@
#include <clang/AST/DeclCXX.h>
#include <clang/AST/DeclFriend.h>
#include <clang/AST/DeclTemplate.h>
#include <clang/AST/PrettyPrinter.h>
#include <clang/AST/QualTypeNames.h>
#include <iostream>
@ -66,12 +68,12 @@ void SerializationCodeGenerator::addDeclaration(clang::Decl *decl)
}
}
SerializationCodeGenerator::IsRelevant SerializationCodeGenerator::isQualifiedNameIfRelevant(
clang::CXXRecordDecl *record, const std::string &qualifiedName) const
void SerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
{
// skip all classes which are only forward-declared
if (!record->isCompleteDefinition()) {
return IsRelevant::No;
if (!possiblyRelevantClass.record->isCompleteDefinition()) {
possiblyRelevantClass.isRelevant = IsRelevant::No;
return;
}
// consider all classes for which a specialization of the "AdaptedJsonSerializable" struct is available
@ -80,31 +82,40 @@ SerializationCodeGenerator::IsRelevant SerializationCodeGenerator::isQualifiedNa
if (isOnlyIncluded(adaptionRecord.record)) {
continue;
}
if (adaptionRecord.qualifiedName == qualifiedName) {
return IsRelevant::Yes;
if (adaptionRecord.qualifiedName == possiblyRelevantClass.qualifiedName) {
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
return;
}
}
// skip all classes which are only included
if (isOnlyIncluded(record)) {
return IsRelevant::No;
if (isOnlyIncluded(possiblyRelevantClass.record)) {
possiblyRelevantClass.isRelevant = IsRelevant::No;
return;
}
// consider all classes inheriting from an instantiation of "JsonSerializable" relevant
if (inheritsFromInstantiationOf(record, m_qualifiedNameOfRecords)) {
return IsRelevant::Yes;
if (const auto *const relevantBase = inheritsFromInstantiationOf(possiblyRelevantClass.record, m_qualifiedNameOfRecords)) {
auto policy = clang::PrintingPolicy(possiblyRelevantClass.record->getASTContext().getLangOpts());
policy.FullyQualifiedName = true;
policy.SuppressScope = false;
policy.SuppressUnwrittenScope = false;
policy.SplitTemplateClosers = false;
possiblyRelevantClass.relevantBase
= clang::TypeName::getFullyQualifiedName(relevantBase->getType(), possiblyRelevantClass.record->getASTContext(), policy, true);
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
return;
}
return IsRelevant::Maybe;
}
std::vector<SerializationCodeGenerator::RelevantClass> SerializationCodeGenerator::findRelevantClasses() const
{
std::vector<RelevantClass> relevantClasses;
for (clang::CXXRecordDecl *record : m_records) {
string qualifiedName(qualifiedNameIfRelevant(record));
if (!qualifiedName.empty()) {
relevantClasses.emplace_back(move(qualifiedName), record);
for (clang::CXXRecordDecl *const record : m_records) {
auto &relevantClass = relevantClasses.emplace_back(record->getQualifiedNameAsString(), record);
computeRelevantClass(relevantClass);
if (relevantClass.isRelevant != IsRelevant::Yes) {
relevantClasses.pop_back();
}
}
return relevantClasses;

View File

@ -5,6 +5,8 @@
#include <llvm/ADT/StringRef.h>
#include <optional>
namespace ReflectiveRapidJSON {
std::ostream &operator<<(std::ostream &os, llvm::StringRef str);
@ -15,11 +17,14 @@ std::ostream &operator<<(std::ostream &os, llvm::StringRef str);
*/
class SerializationCodeGenerator : public CodeGenerator {
public:
enum class IsRelevant { Yes, No, Maybe };
struct RelevantClass {
explicit RelevantClass(std::string &&qualifiedName, clang::CXXRecordDecl *record);
std::string qualifiedName;
clang::CXXRecordDecl *record;
std::string relevantBase;
clang::CXXRecordDecl *record = nullptr;
IsRelevant isRelevant = IsRelevant::Maybe;
};
SerializationCodeGenerator(CodeFactory &factory);
@ -27,9 +32,7 @@ public:
void addDeclaration(clang::Decl *decl) override;
protected:
enum class IsRelevant { Yes, No, Maybe };
IsRelevant isQualifiedNameIfRelevant(clang::CXXRecordDecl *record, const std::string &qualifiedName) const;
virtual std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const = 0;
virtual void computeRelevantClass(RelevantClass &possiblyRelevantClass) const;
std::vector<RelevantClass> findRelevantClasses() const;
static std::vector<const RelevantClass *> findRelevantBaseClasses(
const RelevantClass &relevantClass, const std::vector<RelevantClass> &relevantBases);

View File

@ -13,7 +13,7 @@ template <> void pull<::TestNamespace1::Person>(::TestNamespace1::Person &refle
{
// pull base classes
// set error context for current record
const char *previousRecord;
const char *previousRecord = nullptr;
if (errors) {
previousRecord = errors->currentRecord;
errors->currentRecord = "TestNamespace1::Person";
@ -39,7 +39,7 @@ template <> void pull<::TestNamespace2::ThirdPartyStruct>(::TestNamespace2::Thi
{
// pull base classes
// set error context for current record
const char *previousRecord;
const char *previousRecord = nullptr;
if (errors) {
previousRecord = errors->currentRecord;
errors->currentRecord = "TestNamespace2::ThirdPartyStruct";

View File

@ -1,8 +1,11 @@
#ifndef REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H
#define REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H
#define REFLECTIVE_RAPIDJSON_SHORT_MACROS
#include "../../lib/binary/serializable.h"
#include "../../lib/json/serializable.h"
#include "../../lib/versioning.h"
using namespace std;
using namespace ReflectiveRapidJSON;
@ -67,4 +70,29 @@ struct PointerStruct : public BinarySerializable<PointerStruct> {
std::shared_ptr<PointerTarget> s3;
};
/*!
* \brief All of this is supposed to work if classes are within a namespace so let's use a namespace here.
*/
namespace SomeNamespace {
/*!
* \brief The PointerStruct struct is used to test the behavior of the binary (de)serialization with smart pointer.
*/
// clang-format off
struct VersionedStruct : public BinarySerializable<VersionedStruct, 3> {
std::uint32_t a, b;
until_version(2):
std::uint32_t c, d;
as_of_version(3):
std::uint32_t e, f;
as_of_version(4):
std::uint32_t g;
};
// clang-format on
} // namespace SomeNamespace
#endif // REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H

View File

@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# metadata
set(META_PROJECT_TYPE library)
set(META_HEADER_ONLY_LIB ON)
# add project files
set(HEADER_FILES traits.h)
set(HEADER_FILES traits.h versioning.h)
set(SRC_FILES)
set(TEST_HEADER_FILES)
set(TEST_SRC_FILES)

View File

@ -24,16 +24,17 @@
namespace ReflectiveRapidJSON {
namespace BinaryReflector {
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void readCustomType(BinaryDeserializer &deserializer, Type &customType)
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *>
BinaryVersion readCustomType(BinaryDeserializer &deserializer, Type &customType, BinaryVersion version)
{
boost::hana::for_each(
boost::hana::keys(customType), [&deserializer, &customType](auto key) { deserializer.read(boost::hana::at_key(customType, key)); });
boost::hana::for_each(boost::hana::keys(customType), [&](auto key) { deserializer.read(boost::hana::at_key(customType, key), version); });
return 0;
}
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void writeCustomType(BinarySerializer &serializer, const Type &customType)
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *>
void writeCustomType(BinarySerializer &serializer, const Type &customType, BinaryVersion version)
{
boost::hana::for_each(
boost::hana::keys(customType), [&serializer, &customType](auto key) { serializer.write(boost::hana::at_key(customType, key)); });
boost::hana::for_each(boost::hana::keys(customType), [&](auto key) { serializer.write(boost::hana::at_key(customType, key), version); });
}
} // namespace BinaryReflector

View File

@ -16,23 +16,33 @@
namespace ReflectiveRapidJSON {
namespace BinaryReflector {
template <> inline void readCustomType<CppUtilities::DateTime>(BinaryDeserializer &deserializer, CppUtilities::DateTime &dateTime)
template <>
inline BinaryVersion readCustomType<CppUtilities::DateTime>(BinaryDeserializer &deserializer, CppUtilities::DateTime &dateTime, BinaryVersion version)
{
CPP_UTILITIES_UNUSED(version)
deserializer.read(dateTime.ticks());
return 0;
}
template <> inline void writeCustomType<CppUtilities::DateTime>(BinarySerializer &serializer, const CppUtilities::DateTime &dateTime)
template <>
inline void writeCustomType<CppUtilities::DateTime>(BinarySerializer &serializer, const CppUtilities::DateTime &dateTime, BinaryVersion version)
{
CPP_UTILITIES_UNUSED(version)
serializer.write(dateTime.totalTicks());
}
template <> inline void readCustomType<CppUtilities::TimeSpan>(BinaryDeserializer &deserializer, CppUtilities::TimeSpan &timeSpan)
template <>
inline BinaryVersion readCustomType<CppUtilities::TimeSpan>(BinaryDeserializer &deserializer, CppUtilities::TimeSpan &timeSpan, BinaryVersion version)
{
CPP_UTILITIES_UNUSED(version)
deserializer.read(timeSpan.ticks());
return 0;
}
template <> inline void writeCustomType<CppUtilities::TimeSpan>(BinarySerializer &serializer, const CppUtilities::TimeSpan &timeSpan)
template <>
inline void writeCustomType<CppUtilities::TimeSpan>(BinarySerializer &serializer, const CppUtilities::TimeSpan &timeSpan, BinaryVersion version)
{
CPP_UTILITIES_UNUSED(version)
serializer.write(timeSpan.totalTicks());
}

View File

@ -8,6 +8,7 @@
*/
#include "../traits.h"
#include "../versioning.h"
#include <c++utilities/conversion/conversionexception.h>
#include <c++utilities/io/binaryreader.h>
@ -16,6 +17,7 @@
#include <any>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <variant>
@ -33,7 +35,8 @@ template <typename T> struct AdaptedBinarySerializable : public Traits::Bool<fal
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::AdaptedBinarySerializable";
};
template <typename Type> struct BinarySerializable;
using BinaryVersion = std::uint64_t;
template <typename Type, BinaryVersion v = 0> struct BinarySerializable;
/*!
* \brief The BinaryReflector namespace contains BinaryReader and BinaryWriter for automatic binary (de)serialization.
@ -44,15 +47,29 @@ namespace BinaryReflector {
template <typename Type>
using IsBuiltInType = Traits::Any<Traits::IsAnyOf<Type, char, std::uint8_t, bool, std::string, std::int16_t, std::uint16_t, std::int32_t,
std::uint32_t, std::int64_t, std::uint64_t, float, double>,
Traits::IsIteratable<Type>, Traits::IsSpecializingAnyOf<Type, std::pair, std::unique_ptr, std::shared_ptr>, std::is_enum<Type>, IsVariant<Type>>;
Traits::IsIteratable<Type>, Traits::IsSpecializingAnyOf<Type, std::pair, std::unique_ptr, std::shared_ptr, std::optional>, std::is_enum<Type>,
IsVariant<Type>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
class BinaryDeserializer;
class BinarySerializer;
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void readCustomType(BinaryDeserializer &deserializer, Type &customType);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void writeCustomType(BinarySerializer &serializer, const Type &customType);
/// \brief Reads \a customType via \a deserializer.
/// \remarks
/// - If \tp Type is versioned, the version is determined from the data. Otherwise \a version is assumed.
/// - The determined or specified \a version shall be passed to nested invocations.
/// \returns Returns the determined/assumed version.
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr>
BinaryVersion readCustomType(BinaryDeserializer &deserializer, Type &customType, BinaryVersion version = 0);
/// \brief Writes \a customType via \a serializer.
/// \remarks
/// - If \tp Type is versioned, \a version is prepended to the data.
/// - The specified \a version shall be passed to nested invocations.
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr>
void writeCustomType(BinarySerializer &serializer, const Type &customType, BinaryVersion version = 0);
/// \brief The BinaryDeserializer class can read various data types, including custom ones, from an std::istream.
class BinaryDeserializer : public CppUtilities::BinaryReader {
friend class ::BinaryReflectorTests;
@ -63,6 +80,7 @@ public:
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr> void read(Type &pair);
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::unique_ptr>> * = nullptr> void read(Type &pointer);
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> * = nullptr> void read(Type &pointer);
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> * = nullptr> void read(Type &pointer);
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsResizable<Type>> * = nullptr> void read(Type &iteratable);
template <typename Type, Traits::EnableIfAny<IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr> void read(Type &iteratable);
template <typename Type,
@ -71,12 +89,14 @@ public:
void read(Type &iteratable);
template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void read(Type &enumValue);
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr> void read(Type &variant);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void read(Type &customType);
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> * = nullptr> BinaryVersion read(Type &builtInType, BinaryVersion version);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> BinaryVersion read(Type &customType, BinaryVersion version = 0);
private:
std::unordered_map<std::uint64_t, std::any> m_pointer;
};
/// \brief The BinarySerializer class can write various data types, including custom ones, to an std::ostream.
class BinarySerializer : public CppUtilities::BinaryWriter {
friend class ::BinaryReflectorTests;
@ -85,12 +105,14 @@ public:
using CppUtilities::BinaryWriter::write;
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr> void write(const Type &pair);
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr>> * = nullptr> void write(const Type &pointer);
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::optional>> * = nullptr>
void write(const Type &pointer);
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::shared_ptr>> * = nullptr> void write(const Type &pointer);
template <typename Type, Traits::EnableIf<IsIteratableExceptString<Type>, Traits::HasSize<Type>> * = nullptr> void write(const Type &iteratable);
template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void write(const Type &enumValue);
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr> void write(const Type &variant);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void write(const Type &customType);
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> * = nullptr> void write(const Type &builtInType, BinaryVersion version);
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void write(const Type &customType, BinaryVersion version = 0);
private:
std::unordered_map<std::uint64_t, bool> m_pointer;
@ -141,6 +163,16 @@ template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::
}
}
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> *> void BinaryDeserializer::read(Type &opt)
{
if (!readBool()) {
opt.reset();
return;
}
opt = std::make_optional<typename Type::value_type>();
read(*opt);
}
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsResizable<Type>> *> void BinaryDeserializer::read(Type &iteratable)
{
const auto size = readVariableLengthUIntBE();
@ -207,9 +239,15 @@ template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinaryDeseria
Detail::readVariantValueByRuntimeIndex(readByte(), variant, *this);
}
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinaryDeserializer::read(Type &customType)
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> *> BinaryVersion BinaryDeserializer::read(Type &builtInType, BinaryVersion version)
{
readCustomType(*this, customType);
read(builtInType);
return version;
}
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> BinaryVersion BinaryDeserializer::read(Type &customType, BinaryVersion version)
{
return readCustomType(*this, customType, version);
}
inline BinarySerializer::BinarySerializer(std::ostream *stream)
@ -223,12 +261,12 @@ template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::
write(pair.second);
}
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr>> *> void BinarySerializer::write(const Type &pointer)
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::optional>> *>
void BinarySerializer::write(const Type &opt)
{
const bool hasValue = pointer != nullptr;
writeBool(hasValue);
if (hasValue) {
write(*pointer);
writeBool(static_cast<bool>(opt));
if (opt) {
write(*opt);
}
}
@ -286,9 +324,15 @@ template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinarySeriali
variant);
}
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinarySerializer::write(const Type &customType)
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> *> void BinarySerializer::write(const Type &builtInType, BinaryVersion version)
{
writeCustomType(*this, customType);
CPP_UTILITIES_UNUSED(version)
write(builtInType);
}
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinarySerializer::write(const Type &customType, BinaryVersion version)
{
writeCustomType(*this, customType, version);
}
} // namespace BinaryReflector

View File

@ -14,28 +14,36 @@
namespace ReflectiveRapidJSON {
using BinaryVersionNotSupported = VersionNotSupported<BinaryVersion>;
/*!
* \brief The BinarySerializable class provides the CRTP-base for (de)serializable objects.
*/
template <typename Type> struct BinarySerializable {
void toBinary(std::ostream &outputStream) const;
void restoreFromBinary(std::istream &inputStream);
template <typename Type, BinaryVersion v> struct BinarySerializable {
using VersionNotSupported = BinaryVersionNotSupported;
void toBinary(std::ostream &outputStream, BinaryVersion version = 0) const;
BinaryVersion restoreFromBinary(std::istream &inputStream);
static Type fromBinary(std::istream &inputStream);
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::BinarySerializable";
static constexpr auto version = v;
#if __cplusplus > 201707L
bool operator==(const BinarySerializable<Type, v> &) const = default;
#endif
};
template <typename Type> inline void BinarySerializable<Type>::toBinary(std::ostream &outputStream) const
template <typename Type, BinaryVersion v> inline void BinarySerializable<Type, v>::toBinary(std::ostream &outputStream, BinaryVersion version) const
{
BinaryReflector::BinarySerializer(&outputStream).write(static_cast<const Type &>(*this));
BinaryReflector::BinarySerializer(&outputStream).write(static_cast<const Type &>(*this), version);
}
template <typename Type> inline void BinarySerializable<Type>::restoreFromBinary(std::istream &inputStream)
template <typename Type, BinaryVersion v> inline BinaryVersion BinarySerializable<Type, v>::restoreFromBinary(std::istream &inputStream)
{
BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast<Type &>(*this));
return BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast<Type &>(*this));
}
template <typename Type> Type BinarySerializable<Type>::fromBinary(std::istream &inputStream)
template <typename Type, BinaryVersion v> Type BinarySerializable<Type, v>::fromBinary(std::istream &inputStream)
{
Type object;
static_cast<BinarySerializable<Type> &>(object).restoreFromBinary(inputStream);
@ -50,8 +58,7 @@ template <typename Type> Type BinarySerializable<Type>::fromBinary(std::istream
* Find out whether this is a compiler bug or a correct error message.
*/
#define REFLECTIVE_RAPIDJSON_MAKE_BINARY_SERIALIZABLE(T) \
template <> struct ReflectiveRapidJSON::AdaptedBinarySerializable<T> : Traits::Bool<true> { \
}
template <> struct ReflectiveRapidJSON::AdaptedBinarySerializable<T> : Traits::Bool<true> {}
} // namespace ReflectiveRapidJSON

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# prevent multiple inclusion
if (DEFINED REFLECTION_GENERATOR_MODULE_LOADED)
@ -44,10 +44,10 @@ if (NOT REFLECTION_GENERATOR_CLANG_RESOURCE_DIR)
message(FATAL_ERROR "Unable to find the clang executable to determine Clang's resource directory")
endif ()
endif ()
exec_program(
${REFLECTION_GENERATOR_CLANG_BIN} ARGS
-print-resource-dir
OUTPUT_VARIABLE REFLECTION_GENERATOR_CLANG_RESOURCE_DIR)
execute_process(
COMMAND ${REFLECTION_GENERATOR_CLANG_BIN} -print-resource-dir
OUTPUT_VARIABLE REFLECTION_GENERATOR_CLANG_RESOURCE_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE)
endif ()
if (NOT REFLECTION_GENERATOR_CLANG_RESOURCE_DIR OR NOT IS_DIRECTORY "${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR}")
message(
@ -87,7 +87,7 @@ endfunction ()
include(CMakeParseArguments)
function (add_reflection_generator_invocation)
# parse arguments
set(OPTIONAL_ARGS)
set(OPTIONAL_ARGS ERROR_RESILIENT)
set(ONE_VALUE_ARGS OUTPUT_DIRECTORY JSON_VISIBILITY BINARY_VISBILITY)
set(MULTI_VALUE_ARGS
INPUT_FILES
@ -121,30 +121,25 @@ function (add_reflection_generator_invocation)
list(APPEND ARGS_CLANG_OPTIONS -I "${INCLUDE_DIR}")
endforeach ()
# add workaround for cross compiling with mingw-w64 to prevent host stdlib.h being included (not sure why specifying
# REFLECTION_GENERATOR_INCLUDE_DIRECTORIES is not enough to let it find this particular header file)
if (MINGW)
# find MinGW version of stdlib.h
find_file(MINGW_W64_STDLIB_H stdlib.h ${REFLECTION_GENERATOR_INCLUDE_DIRECTORIES})
if (NOT EXISTS "${MINGW_W64_STDLIB_H}")
message(
FATAL_ERROR
"Unable to locate MinGW version of stdlib.h. Ensure it is in REFLECTION_GENERATOR_INCLUDE_DIRECTORIES.")
endif ()
# ensure libtooling includes the MinGW version of stdlib.h rather than the host version
list(APPEND ARGS_CLANG_OPTIONS -include "${MINGW_W64_STDLIB_H}" -D_STDLIB_H # prevent processing of host stdlib.h
)
# avoid including headers from host when cross compiling
if (CMAKE_CROSSCOMPILING)
list(APPEND ARGS_CLANG_OPTIONS -nostdinc)
endif ()
# add options to be passed to clang from the specified targets
if (ARGS_CLANG_OPTIONS_FROM_TARGETS)
foreach (TARGET_NAME ${ARGS_CLANG_OPTIONS_FROM_TARGETS})
# set c++ standard
list(APPEND ARGS_CLANG_OPTIONS "-std=c++$<TARGET_PROPERTY:${TARGET_NAME},CXX_STANDARD>")
# add compile flags
list(
APPEND
ARGS_CLANG_OPTIONS
"$<$<BOOL:$<TARGET_PROPERTY:${TARGET_NAME},CXX_STANDARD>>:-std=c++$<TARGET_PROPERTY:${TARGET_NAME},CXX_STANDARD>>"
)
# add compile flags and options
_reflective_rapidjson_set_prop("${TARGET_NAME}" COMPILE_FLAGS)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:$<JOIN:${PROP},$<SEMICOLON>>>")
_reflective_rapidjson_set_prop("${TARGET_NAME}" COMPILE_OPTIONS)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:$<JOIN:${PROP},$<SEMICOLON>>>")
# add compile definitions
_reflective_rapidjson_set_prop("${TARGET_NAME}" COMPILE_DEFINITIONS)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:-D$<JOIN:${PROP},$<SEMICOLON>-D>>")
@ -195,6 +190,9 @@ function (add_reflection_generator_invocation)
if (ARGS_BINARY_VISBILITY)
list(APPEND CLI_ARGUMENTS --binary-visibility "${ARGS_BINARY_VISBILITY}")
endif ()
if (ARGS_ERROR_RESILIENT)
list(APPEND CLI_ARGUMENTS --error-resilient)
endif ()
add_custom_command(
OUTPUT "${OUTPUT_FILE}"
COMMAND "${REFLECTION_GENERATOR_EXECUTABLE}" ARGS ${CLI_ARGUMENTS}

View File

@ -0,0 +1 @@
../../versioning.h

View File

@ -155,6 +155,7 @@ struct JsonDeserializationErrors : public std::vector<JsonDeserializationError>
JsonDeserializationErrors();
template <typename ExpectedType> void reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType);
template <RAPIDJSON_NAMESPACE::Type expectedType> void reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType);
void reportArraySizeMismatch();
void reportConversionError(JsonType jsonType);
void reportUnexpectedDuplicate(JsonType jsonType);
@ -220,6 +221,16 @@ template <typename ExpectedType> inline void JsonDeserializationErrors::reportTy
throwMaybe(ThrowOn::TypeMismatch);
}
/*!
* \brief Reports a type mismatch between \tparam expectedType and \a presentType within the current context.
*/
template <RAPIDJSON_NAMESPACE::Type expectedType> inline void JsonDeserializationErrors::reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType)
{
emplace_back(
JsonDeserializationErrorKind::TypeMismatch, jsonType(expectedType), jsonType(presentType), currentRecord, currentMember, currentIndex);
throwMaybe(ThrowOn::TypeMismatch);
}
/*!
* \brief Reports an array size mismatch.
* \todo Allow specifying expected and actual size.

View File

@ -19,11 +19,13 @@
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <variant>
#include "./errorhandling.h"
@ -81,9 +83,8 @@ inline RAPIDJSON_NAMESPACE::Document parseJsonDocFromString(const char *json, st
// define traits to distinguish between "built-in" types like int, std::string, std::vector, ... and custom structs/classes
template <typename Type>
using IsBuiltInType = Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>, std::is_enum<Type>,
Traits::IsSpecializationOf<Type, std::tuple>, Traits::IsSpecializationOf<Type, std::pair>, Traits::IsIteratable<Type>,
Traits::IsSpecializationOf<Type, std::unique_ptr>, Traits::IsSpecializationOf<Type, std::shared_ptr>,
Traits::IsSpecializationOf<Type, std::weak_ptr>, IsVariant<Type>>;
Traits::IsSpecializingAnyOf<Type, std::tuple, std::pair>, Traits::IsIteratable<Type>,
Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional>, IsVariant<Type>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
// define trait to check for custom structs/classes which are JSON serializable
@ -147,12 +148,24 @@ inline void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAP
/*!
* \brief Pushes the specified integer/float/boolean to the specified value.
*/
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>> * = nullptr>
template <typename Type,
Traits::EnableIfAny<
Traits::All<std::is_integral<Type>, Traits::Not<std::is_same<Type, std::uint8_t>>, Traits::Not<std::is_same<Type, std::int8_t>>>,
std::is_floating_point<Type>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
value.Set(reflectable, allocator);
}
/*!
* \brief Pushes the specified 8-bit integer to the specified value.
*/
template <typename Type, Traits::EnableIfAny<std::is_same<Type, std::uint8_t>, std::is_same<Type, std::int8_t>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
value.Set(static_cast<int>(reflectable), allocator);
}
/*!
* \brief Pushes the specified enumeration item to the specified value.
*/
@ -306,11 +319,10 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
}
/*!
* \brief Pushes the specified unique_ptr, shared_ptr or weak_ptr to the specified value.
* \brief Pushes the specified unique_ptr, shared_ptr, weak_ptr or optional to the specified value.
*/
template <typename Type,
Traits::EnableIfAny<Traits::IsSpecializationOf<Type, std::unique_ptr>, Traits::IsSpecializationOf<Type, std::shared_ptr>,
Traits::IsSpecializationOf<Type, std::weak_ptr>> * = nullptr>
Traits::EnableIfAny<Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional>> * = nullptr>
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
if (!reflectable) {
@ -479,6 +491,12 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
/*!
* \brief Pulls the specified \a reflectable which is an std::optional from the specified value which might be null.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> * = nullptr>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
/*!
* \brief Pulls the specified \a reflectable which is a variant from the specified value which might be null.
*/
@ -511,7 +529,8 @@ void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_N
* \brief Pulls the integer or float from the specified value which is supposed and checked to contain the right type.
*/
template <typename Type,
Traits::EnableIf<Traits::Not<std::is_same<Type, bool>>, Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>>> * = nullptr>
Traits::EnableIf<Traits::Not<std::is_same<Type, bool>>, Traits::Not<std::is_same<Type, std::uint8_t>>,
Traits::Not<std::is_same<Type, std::int8_t>>, Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>>> * = nullptr>
inline void pull(
Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
@ -524,6 +543,20 @@ inline void pull(
reflectable = value.Is<Type>() ? value.Get<Type>() : static_cast<Type>(value.GetDouble());
}
/*!
* \brief Pulls the integer or float from the specified value which is supposed and checked to contain the right type.
*/
template <typename Type, Traits::EnableIfAny<std::is_same<Type, std::uint8_t>, std::is_same<Type, std::int8_t>> * = nullptr>
inline void pull(
Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
int i = 0;
pull(i, value, errors);
if (value.IsNumber()) {
reflectable = static_cast<Type>(i);
}
}
/*!
* \brief Pulls the boolean from the specified value which is supposed and checked to contain the right type.
*/
@ -668,7 +701,7 @@ void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<c
++index;
typename Type::value_type itemObj;
pull(itemObj, item, errors);
reflectable.emplace(move(itemObj));
reflectable.emplace(std::move(itemObj));
}
// clear error context
@ -696,7 +729,7 @@ void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<c
++index;
typename Type::value_type itemObj;
pull(itemObj, item, errors);
if (!reflectable.emplace(move(itemObj)).second) {
if (!reflectable.emplace(std::move(itemObj)).second) {
errors->reportUnexpectedDuplicate(JsonType::Array);
}
}
@ -849,6 +882,20 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
pull(*reflectable, value, errors);
}
/*!
* \brief Pulls the specified \a reflectable which is an std::optional from the specified value which might be null.
*/
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> *>
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if (value.IsNull()) {
reflectable.reset();
return;
}
reflectable = std::make_optional<typename Type::value_type>();
pull(*reflectable, value, errors);
}
/// \cond
namespace Detail {
template <typename Variant, std::size_t compiletimeIndex = 0>
@ -935,7 +982,7 @@ inline void pull(Type &reflectable, const char *name, const rapidjson::GenericVa
}
// set error context for current member
const char *previousMember;
const char *previousMember = nullptr;
if (errors) {
previousMember = errors->currentMember;
errors->currentMember = name;

View File

@ -31,6 +31,10 @@ template <typename Type> struct JsonSerializable {
static Type fromJson(const std::string &json, JsonDeserializationErrors *errors = nullptr);
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::JsonSerializable";
#if __cplusplus > 201707L
bool operator==(const JsonSerializable<Type> &) const = default;
#endif
};
/*!
@ -116,8 +120,7 @@ const JsonSerializable<Type> &as(const Type &serializable)
* Find out whether this is a compiler bug or a correct error message.
*/
#define REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(T) \
template <> struct ReflectiveRapidJSON::AdaptedJsonSerializable<T> : Traits::Bool<true> { \
}
template <> struct ReflectiveRapidJSON::AdaptedJsonSerializable<T> : Traits::Bool<true> {}
/*!
* \def The REFLECTIVE_RAPIDJSON_PUSH_PRIVATE_MEMBERS macro enables serialization of private members.

View File

@ -75,8 +75,9 @@ struct ObjectWithVariantsBinary : public BinarySerializable<ObjectWithVariantsBi
namespace ReflectiveRapidJSON {
namespace BinaryReflector {
template <> void readCustomType<TestObjectBinary>(BinaryDeserializer &deserializer, TestObjectBinary &customType)
template <> BinaryVersion readCustomType<TestObjectBinary>(BinaryDeserializer &deserializer, TestObjectBinary &customType, BinaryVersion version)
{
CPP_UTILITIES_UNUSED(version)
deserializer.read(customType.number);
deserializer.read(customType.number2);
deserializer.read(customType.numbers);
@ -92,10 +93,12 @@ template <> void readCustomType<TestObjectBinary>(BinaryDeserializer &deserializ
deserializer.read(customType.someEnumClass);
deserializer.read(customType.timeSpan);
deserializer.read(customType.dateTime);
return 0;
}
template <> void writeCustomType<TestObjectBinary>(BinarySerializer &serializer, const TestObjectBinary &customType)
template <> void writeCustomType<TestObjectBinary>(BinarySerializer &serializer, const TestObjectBinary &customType, BinaryVersion version)
{
CPP_UTILITIES_UNUSED(version)
serializer.write(customType.number);
serializer.write(customType.number2);
serializer.write(customType.numbers);
@ -113,27 +116,35 @@ template <> void writeCustomType<TestObjectBinary>(BinarySerializer &serializer,
serializer.write(customType.dateTime);
}
template <> void readCustomType<NestingArrayBinary>(BinaryDeserializer &deserializer, NestingArrayBinary &customType)
template <> BinaryVersion readCustomType<NestingArrayBinary>(BinaryDeserializer &deserializer, NestingArrayBinary &customType, BinaryVersion version)
{
CPP_UTILITIES_UNUSED(version)
deserializer.read(customType.name);
deserializer.read(customType.testObjects);
return 0;
}
template <> void writeCustomType<NestingArrayBinary>(BinarySerializer &serializer, const NestingArrayBinary &customType)
template <> void writeCustomType<NestingArrayBinary>(BinarySerializer &serializer, const NestingArrayBinary &customType, BinaryVersion version)
{
CPP_UTILITIES_UNUSED(version)
serializer.write(customType.name);
serializer.write(customType.testObjects);
}
template <> void readCustomType<ObjectWithVariantsBinary>(BinaryDeserializer &deserializer, ObjectWithVariantsBinary &customType)
template <>
BinaryVersion readCustomType<ObjectWithVariantsBinary>(BinaryDeserializer &deserializer, ObjectWithVariantsBinary &customType, BinaryVersion version)
{
CPP_UTILITIES_UNUSED(version)
deserializer.read(customType.someVariant);
deserializer.read(customType.anotherVariant);
deserializer.read(customType.yetAnotherVariant);
return 0;
}
template <> void writeCustomType<ObjectWithVariantsBinary>(BinarySerializer &serializer, const ObjectWithVariantsBinary &customType)
template <>
void writeCustomType<ObjectWithVariantsBinary>(BinarySerializer &serializer, const ObjectWithVariantsBinary &customType, BinaryVersion version)
{
CPP_UTILITIES_UNUSED(version)
serializer.write(customType.someVariant);
serializer.write(customType.anotherVariant);
serializer.write(customType.yetAnotherVariant);
@ -159,6 +170,7 @@ class BinaryReflectorTests : public TestFixture {
CPPUNIT_TEST(testSmallSharedPointer);
CPPUNIT_TEST(testBigSharedPointer);
CPPUNIT_TEST(testVariant);
CPPUNIT_TEST(testOptional);
CPPUNIT_TEST_SUITE_END();
public:
@ -176,6 +188,7 @@ public:
void testSmallSharedPointer();
void testBigSharedPointer();
void testVariant();
void testOptional();
private:
vector<unsigned char> m_buffer;
@ -260,13 +273,44 @@ void BinaryReflectorTests::tearDown()
{
}
static void setBuffer(std::stringstream &stream, unsigned char *buffer, std::size_t bufferSize)
{
#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(buffer), static_cast<std::streamsize>(bufferSize));
#else
CPP_UTILITIES_UNUSED(stream)
CPP_UTILITIES_UNUSED(buffer)
CPP_UTILITIES_UNUSED(bufferSize)
#endif
}
static void readBuffer(std::stringstream &stream, unsigned char *buffer, std::size_t bufferSize)
{
#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
CPP_UTILITIES_UNUSED(stream)
CPP_UTILITIES_UNUSED(buffer)
CPP_UTILITIES_UNUSED(bufferSize)
#else
stream.read(reinterpret_cast<char *>(buffer), static_cast<std::streamsize>(bufferSize));
#endif
}
static void writeBuffer(std::stringstream &stream, unsigned char *buffer, std::size_t bufferSize)
{
#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(buffer), static_cast<std::streamsize>(bufferSize));
#else
stream.write(reinterpret_cast<const char *>(buffer), static_cast<std::streamsize>(bufferSize));
#endif
}
void BinaryReflectorTests::testSerializeSimpleStruct()
{
stringstream stream(ios_base::out | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit);
m_buffer.resize(m_expectedTestObj.size());
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(m_buffer.data()), static_cast<streamsize>(m_buffer.size()));
setBuffer(stream, m_buffer.data(), m_buffer.size());
m_testObj.toBinary(stream);
readBuffer(stream, m_buffer.data(), m_buffer.size());
CPPUNIT_ASSERT_EQUAL(m_expectedTestObj, m_buffer);
}
@ -275,7 +319,7 @@ void BinaryReflectorTests::testDeserializeSimpleStruct()
{
stringstream stream(ios_base::in | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit);
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(m_expectedTestObj.data()), static_cast<streamsize>(m_expectedTestObj.size()));
writeBuffer(stream, m_expectedTestObj.data(), m_expectedTestObj.size());
const auto deserialized(TestObjectBinary::fromBinary(stream));
assertTestObject(deserialized);
}
@ -285,8 +329,9 @@ void BinaryReflectorTests::testSerializeNestedStruct()
stringstream stream(ios_base::out | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit);
m_buffer.resize(m_expectedNestedTestObj.size());
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(m_buffer.data()), static_cast<streamsize>(m_buffer.size()));
setBuffer(stream, m_buffer.data(), m_buffer.size());
m_nestedTestObj.toBinary(stream);
readBuffer(stream, m_buffer.data(), m_buffer.size());
CPPUNIT_ASSERT_EQUAL(m_expectedNestedTestObj, m_buffer);
}
@ -295,7 +340,7 @@ void BinaryReflectorTests::testDeserializeNestedStruct()
{
stringstream stream(ios_base::in | ios_base::binary);
stream.exceptions(ios_base::failbit | ios_base::badbit);
stream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(m_expectedNestedTestObj.data()), static_cast<streamsize>(m_expectedNestedTestObj.size()));
writeBuffer(stream, m_expectedNestedTestObj.data(), m_expectedNestedTestObj.size());
const auto deserialized(NestingArrayBinary::fromBinary(stream));
CPPUNIT_ASSERT_EQUAL(m_nestedTestObj.name, deserialized.name);
@ -380,3 +425,28 @@ void BinaryReflectorTests::testVariant()
CPPUNIT_ASSERT_EQUAL("foo"s, get<0>(deserializedVariants.anotherVariant));
CPPUNIT_ASSERT_EQUAL(42, get<1>(deserializedVariants.yetAnotherVariant));
}
void BinaryReflectorTests::testOptional()
{
// create test objects
const auto str = std::make_optional<std::string>("foo");
const auto nullStr = std::optional<std::string>();
// serialize test object
auto stream = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
stream.exceptions(std::ios_base::failbit | std::ios_base::badbit);
auto ser = BinaryReflector::BinarySerializer(&stream);
ser.write(str);
ser.write(nullStr);
// deserialize the object again
auto deser = BinaryReflector::BinaryDeserializer(&stream);
auto deserStr = std::optional<std::string>();
auto deserNullStr = std::optional<std::string>();
deser.read(deserStr);
deser.read(deserNullStr);
CPPUNIT_ASSERT(deserStr.has_value());
CPPUNIT_ASSERT_EQUAL("foo"s, deserStr.value());
CPPUNIT_ASSERT(!nullStr.has_value());
}

View File

@ -112,7 +112,7 @@ template <> inline void push<NestingArray>(const NestingArray &reflectable, Valu
template <>
inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors)
{
const char *previousRecord;
const char *previousRecord = nullptr;
if (errors) {
previousRecord = errors->currentRecord;
errors->currentRecord = "TestObject";
@ -141,7 +141,7 @@ inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<ch
template <>
inline void pull<NestingObject>(NestingObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors)
{
const char *previousRecord;
const char *previousRecord = nullptr;
if (errors) {
previousRecord = errors->currentRecord;
errors->currentRecord = "NestingObject";
@ -156,7 +156,7 @@ inline void pull<NestingObject>(NestingObject &reflectable, const GenericValue<U
template <>
inline void pull<NestingArray>(NestingArray &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors)
{
const char *previousRecord;
const char *previousRecord = nullptr;
if (errors) {
previousRecord = errors->currentRecord;
errors->currentRecord = "NestingArray";
@ -186,11 +186,13 @@ class JsonReflectorTests : public TestFixture {
CPPUNIT_TEST(testSerializeNestedObjects);
CPPUNIT_TEST(testSerializeUniquePtr);
CPPUNIT_TEST(testSerializeSharedPtr);
CPPUNIT_TEST(testSerializeOptional);
CPPUNIT_TEST(testDeserializePrimitives);
CPPUNIT_TEST(testDeserializeSimpleObjects);
CPPUNIT_TEST(testDeserializeNestedObjects);
CPPUNIT_TEST(testDeserializeUniquePtr);
CPPUNIT_TEST(testDeserializeSharedPtr);
CPPUNIT_TEST(testDeserializeOptional);
CPPUNIT_TEST(testHandlingParseError);
CPPUNIT_TEST(testHandlingTypeMismatch);
CPPUNIT_TEST_SUITE_END();
@ -205,11 +207,13 @@ public:
void testSerializeNestedObjects();
void testSerializeUniquePtr();
void testSerializeSharedPtr();
void testSerializeOptional();
void testDeserializePrimitives();
void testDeserializeSimpleObjects();
void testDeserializeNestedObjects();
void testDeserializeUniquePtr();
void testDeserializeSharedPtr();
void testDeserializeOptional();
void testHandlingParseError();
void testHandlingTypeMismatch();
@ -379,6 +383,28 @@ void JsonReflectorTests::testSerializeSharedPtr()
string(strbuf.GetString()));
}
/*!
* \brief Tests serializing std::optional.
*/
void JsonReflectorTests::testSerializeOptional()
{
Document doc(kArrayType);
Document::AllocatorType &alloc = doc.GetAllocator();
doc.SetArray();
Document::Array array(doc.GetArray());
const auto str = make_optional<std::string>("foo");
const auto nullStr = std::optional<std::string>();
JsonReflector::push(str, array, alloc);
JsonReflector::push(nullStr, array, alloc);
StringBuffer strbuf;
Writer<StringBuffer> jsonWriter(strbuf);
doc.Accept(jsonWriter);
CPPUNIT_ASSERT_EQUAL("[\"foo\",null]"s, std::string(strbuf.GetString()));
}
/*!
* \brief Tests deserializing strings, numbers (int, float, double) and boolean.
*/
@ -517,6 +543,9 @@ void JsonReflectorTests::testDeserializeNestedObjects()
CPPUNIT_ASSERT_EQUAL("test"s, nestedInVector[0].text);
}
/*!
* \brief Tests deserializing std::optional.
*/
void JsonReflectorTests::testDeserializeUniquePtr()
{
Document doc(kArrayType);
@ -561,6 +590,22 @@ void JsonReflectorTests::testDeserializeSharedPtr()
CPPUNIT_ASSERT_EQUAL("bar"s, obj->text);
}
void JsonReflectorTests::testDeserializeOptional()
{
Document doc(kArrayType);
doc.Parse("[\"foo\",null]");
auto array = doc.GetArray().begin();
optional<string> str = "foo"s;
optional<string> nullStr;
JsonDeserializationErrors errors;
JsonReflector::pull(str, array, &errors);
CPPUNIT_ASSERT_EQUAL(0_st, errors.size());
CPPUNIT_ASSERT(str.has_value());
CPPUNIT_ASSERT_EQUAL("foo"s, *str);
CPPUNIT_ASSERT(!nullStr.has_value());
}
/*!
* \brief Tests whether RAPIDJSON_NAMESPACE::ParseResult is thrown correctly when passing invalid JSON to fromJSON().
*/

View File

@ -1,13 +1,21 @@
#include "../traits.h"
#include "../versioning.h"
#include "../binary/serializable.h"
#include <list>
#include <vector>
// treat some types differently to test Treat... traits
struct Foo {
};
struct Bar {
};
// define structs for testing REFLECTIVE_RAPIDJSON_TREAT_AS_…
struct Foo {};
struct Bar {};
// define structs for testing versioning
struct VersionlessBase : public ReflectiveRapidJSON::BinarySerializable<VersionlessBase> {};
struct VersionedDerived : public VersionlessBase, public ReflectiveRapidJSON::BinarySerializable<VersionedDerived, 1> {};
struct VersionedBase : public ReflectiveRapidJSON::BinarySerializable<VersionlessBase, 1> {};
struct VersionlessDerived : public VersionedBase, public ReflectiveRapidJSON::BinarySerializable<VersionlessDerived> {};
namespace ReflectiveRapidJSON {
REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH(Foo);
REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH(Foo);
@ -48,3 +56,14 @@ static_assert(IsIteratableExceptString<std::vector<int>>::value, "vector is iter
static_assert(!IsIteratableExceptString<std::string>::value, "string not iteratable");
static_assert(!IsIteratableExceptString<std::wstring>::value, "wstring not iteratable");
static_assert(!IsIteratableExceptString<const std::string>::value, "string not iteratable");
// test versioning traits
static_assert(!Versioning<int>::enabled, "versioning for built-in types not enabled");
static_assert(!Versioning<std::string>::enabled, "versioning for standard types not enabled");
static_assert(!Versioning<VersionlessBase>::enabled, "versioning not enabled by default");
static_assert(Versioning<BinarySerializable<VersionedDerived, 1>>::enabled, "versioning enabled if non-zero version parameter specified (derived)");
static_assert(Versioning<VersionedBase>::enabled, "versioning enabled if non-zero version parameter specified (base)");
static_assert(!Versioning<BinarySerializable<VersionlessDerived>>::enabled, "versioning disabled for derived, even if base is versioned");
static_assert(!Versioning<BinarySerializable<Foo, 0>>::enabled, "versioning disabled if zero-version specified");
static_assert(Versioning<BinarySerializable<Foo, 3>>::applyDefault(0) == 3, "default version returned");
static_assert(Versioning<BinarySerializable<Foo, 3>>::applyDefault(2) == 2, "default version overridden");

View File

@ -16,30 +16,22 @@ namespace Traits = ::CppUtilities::Traits;
// define structs and macros to allow treating custom data types as std::map, std::set, ...
/// \brief \brief The TreatAsMapOrHash class allows treating custom classes as std::map or std::unordered_map.
template <typename T> struct TreatAsMapOrHash : public Traits::Bool<false> {
};
template <typename T> struct TreatAsMapOrHash : public Traits::Bool<false> {};
/// \brief \brief The TreatAsMultiMapOrHash class allows treating custom classes as std::multimap or std::unordered_multimap.
template <typename T> struct TreatAsMultiMapOrHash : public Traits::Bool<false> {
};
template <typename T> struct TreatAsMultiMapOrHash : public Traits::Bool<false> {};
/// \brief \brief The TreatAsSet class allows treating custom classes as std::set or std::unordered_set.
template <typename T> struct TreatAsSet : public Traits::Bool<false> {
};
template <typename T> struct TreatAsSet : public Traits::Bool<false> {};
/// \brief \brief The TreatAsMultiSet class allows treating custom classes as std::multiset or std::unordered_multiset.
template <typename T> struct TreatAsMultiSet : public Traits::Bool<false> {
};
template <typename T> struct TreatAsMultiSet : public Traits::Bool<false> {};
#define REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH(T) \
template <> struct TreatAsMapOrHash<T> : public Traits::Bool<true> { \
}
template <> struct TreatAsMapOrHash<T> : public Traits::Bool<true> {}
#define REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH(T) \
template <> struct TreatAsMultiMapOrHash<T> : public Traits::Bool<true> { \
}
template <> struct TreatAsMultiMapOrHash<T> : public Traits::Bool<true> {}
#define REFLECTIVE_RAPIDJSON_TREAT_AS_SET(T) \
template <> struct TreatAsSet<T> : public Traits::Bool<true> { \
}
template <> struct TreatAsSet<T> : public Traits::Bool<true> {}
#define REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_SET(T) \
template <> struct TreatAsMultiSet<T> : public Traits::Bool<true> { \
}
template <> struct TreatAsMultiSet<T> : public Traits::Bool<true> {}
// define traits to check for arrays, sets and maps
template <typename Type>

60
lib/versioning.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef REFLECTIVE_RAPIDJSON_VERSIONING
#define REFLECTIVE_RAPIDJSON_VERSIONING
#include <c++utilities/misc/traits.h>
namespace ReflectiveRapidJSON {
#ifdef REFLECTIVE_RAPIDJSON_GENERATOR
#define REFLECTIVE_RAPIDJSON_CAT_1(a, b) a##b
#define REFLECTIVE_RAPIDJSON_CAT_2(a, b) REFLECTIVE_RAPIDJSON_CAT_1(a, b)
#define REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version) \
static constexpr std::size_t REFLECTIVE_RAPIDJSON_CAT_2(rrjAsOfVersion, __COUNTER__) = version; \
public
#define REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version) \
static constexpr std::size_t REFLECTIVE_RAPIDJSON_CAT_2(rrjUntilVersion, __COUNTER__) = version; \
public
#else
#define REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version) public
#define REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version) public
#endif
#ifdef REFLECTIVE_RAPIDJSON_SHORT_MACROS
#define as_of_version(version) REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version)
#define until_version(version) REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version)
#endif
CPP_UTILITIES_TRAITS_DEFINE_TYPE_CHECK(IsVersioned, T::version);
template <typename VersionType> struct VersionNotSupported {
VersionType presentVersion = 0, maxVersion = 0;
const char *record = nullptr;
};
template <typename Type, bool Condition = IsVersioned<Type>::value> struct Versioning {
static constexpr auto enabled = false;
};
template <typename Type> struct Versioning<Type, true> {
static constexpr auto enabled = Type::version != 0;
static constexpr auto serializationDefault = Type::version;
static constexpr auto maxSupported = Type::version;
static constexpr auto applyDefault(decltype(serializationDefault) version)
{
return version ? version : serializationDefault;
}
static constexpr auto isSupported(decltype(maxSupported) version)
{
return version <= maxSupported;
}
static constexpr auto assertVersion(decltype(maxSupported) version, const char *record = nullptr)
{
if (!isSupported(version)) {
throw typename Type::VersionNotSupported({ .presentVersion = version, .maxVersion = maxSupported, .record = record });
}
}
};
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_TRAITS