Compare commits
47 Commits
Author | SHA1 | Date |
---|---|---|
Martchus | 743fd60424 | |
Martchus | 5813ee21c5 | |
Martchus | cc1641e0f8 | |
Martchus | 1ea5b1e744 | |
Martchus | 7c8ef68155 | |
Martchus | e3e4596481 | |
Martchus | 091d521152 | |
Martchus | 9a6d550d8f | |
Martchus | e82d834bf2 | |
Martchus | 27b029ba67 | |
Martchus | d50a4c6004 | |
Martchus | 10bb97bbc4 | |
Martchus | b1bd782910 | |
Martchus | 2b0048f144 | |
Martchus | 5c7a6cba8c | |
Martchus | 60d761f7ed | |
Martchus | 9c3bf01c8f | |
Martchus | 1e3417f8d0 | |
Martchus | a6b9d771aa | |
Martchus | 762540f5e5 | |
Martchus | 0633923935 | |
Martchus | 11491b1387 | |
Martchus | df787f3105 | |
Martchus | 2cc044c705 | |
Martchus | 4966625d8b | |
Martchus | efaa8a8441 | |
Martchus | 22611457f9 | |
Martchus | 59ff3c19eb | |
Martchus | f8f551a78a | |
Martchus | 8b66ca3e6b | |
Martchus | 44c6b8c609 | |
Martchus | 13428667f8 | |
Martchus | 852dfb7e3c | |
Martchus | 0a902ac30c | |
Martchus | 5e72012ed5 | |
Martchus | a4dd52acfa | |
Martchus | e3d32ddfa1 | |
Martchus | 8f1909dfdf | |
Martchus | 874c964e0b | |
Martchus | 30735ba187 | |
Martchus | 5110cff5eb | |
Martchus | dc7c74c497 | |
Martchus | 80183f5269 | |
Martchus | 5c49a438ad | |
Martchus | 6252a7335a | |
Martchus | 2b6634d574 | |
Martchus | 0010e32515 |
|
@ -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
|
|
@ -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)
|
||||
|
@ -11,7 +11,7 @@ set(META_APP_CATEGORIES "Utility;")
|
|||
set(META_GUI_OPTIONAL false)
|
||||
set(META_VERSION_MAJOR 0)
|
||||
set(META_VERSION_MINOR 0)
|
||||
set(META_VERSION_PATCH 15)
|
||||
set(META_VERSION_PATCH 16)
|
||||
set(META_APP_VERSION ${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH})
|
||||
set(META_CXX_STANDARD 17)
|
||||
set(META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION ON)
|
||||
|
@ -46,10 +46,12 @@ endif()
|
|||
|
||||
# find c++utilities
|
||||
set(CONFIGURATION_PACKAGE_SUFFIX "" CACHE STRING "sets the suffix for find_package() calls to packages configured via c++utilities")
|
||||
find_package(c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.0.0 REQUIRED)
|
||||
find_package(c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.10.0 REQUIRED)
|
||||
|
||||
# find RapidJSON
|
||||
find_package(RapidJSON)
|
||||
if(NOT RapidJSON_FOUND)
|
||||
find_package(RapidJSON)
|
||||
endif()
|
||||
if(NOT RapidJSON_FOUND)
|
||||
message(FATAL_ERROR "Unable to find RapidJSON. Since this is the only supported reflection application at this time, it makes no sense to continue.")
|
||||
endif()
|
||||
|
|
86
README.md
86
README.md
|
@ -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 <reflective_rapidjson/binary/serializable.h>
|
||||
|
||||
// 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<Nested> { //
|
||||
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 >= 3
|
||||
};
|
||||
|
||||
// example struct where version is serialized/deserialized; defaults to version 3 when writing
|
||||
struct Example : public BinarySerializable<Example, 3> {
|
||||
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 <= 2
|
||||
|
||||
as_of_version(3):
|
||||
std::uint32_t e, f; // will be read/written if version is >= 3
|
||||
|
||||
as_of_version(4):
|
||||
std::uint32_t g; // will be read/written if version is >= 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.
|
||||
|
@ -382,7 +431,7 @@ The following dependencies are required at build time. Note that Reflective Rapi
|
|||
and *none* of these dependencies are required at runtime by an application which makes use of
|
||||
Reflective RapidJSON.
|
||||
|
||||
* C++ compiler and C++ standard library supporting at least C++14
|
||||
* C++ compiler and C++ standard library supporting at least C++17
|
||||
* the [CMake](https://cmake.org) build system
|
||||
* LibTooling from [Clang](https://clang.llvm.org) for the code generator (optional when using
|
||||
Boost.Hana)
|
||||
|
@ -401,17 +450,8 @@ Reflective RapidJSON.
|
|||
different build system, there is no helper for adding the code generator to the build process
|
||||
provided (so far).
|
||||
* I usually develop using the latest version of those dependencies. So it is recommend to get the
|
||||
the latest versions as well. I tested the following versions so far:
|
||||
* GCC 7.2.1/7.3.0/8.1.0/9.1.0 or Clang 5.0/6.0/7.0/8.0 as compiler
|
||||
* libstdc++ from GCC 7.2.1/7.3.0/8.1.0/9.1.0
|
||||
* CMake 3.10.x to 3.15.x
|
||||
* Clang 5.x/6.x/7.x/8.x/9.x for LibTooling
|
||||
* RapidJSON 1.1.0
|
||||
* C++ utilities 5.0.0
|
||||
* Boost.Hana 1.65.1, 1.66.0, 1.67.0, 1.68.0, 1.69.0
|
||||
* CppUnit 1.14.0
|
||||
* Doxygen 1.8.13 to 1.8.16
|
||||
* Graphviz 2.40.1
|
||||
the latest versions as well although very likely older versions might work as well. When adapting
|
||||
to new versions of LLVM/Clang I usually take care that it also still works with previous versions.
|
||||
* The binary (de)serializer requires C++ utilities at runtime. So when using it, it is required to
|
||||
link against C++ utilities.
|
||||
|
||||
|
@ -471,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"
|
||||
|
@ -482,12 +522,20 @@ Add eg. `-j$(nproc)` to `make` arguments for using all cores.
|
|||
* Arch Linux
|
||||
* for PKGBUILDs checkout [my GitHub repository](https://github.com/Martchus/PKGBUILDs) or
|
||||
[the AUR](https://aur.archlinux.org/packages?SeB=m&K=Martchus)
|
||||
* for a binary repository checkout [my website](http://martchus.no-ip.biz/website/page.php?name=programming)
|
||||
* there is also a [binary repository](https://martchus.dyn.f3l.de/repo/arch/ownstuff)
|
||||
* Tumbleweed
|
||||
* for RPM \*.spec files and binary repository checkout
|
||||
[openSUSE Build Servide](https://build.opensuse.org/project/show/home:mkittler)
|
||||
* RPM \*.spec files and binaries are available via openSUSE Build Service
|
||||
* latest releases: [download page](https://software.opensuse.org/download.html?project=home:mkittler&package=reflective-rapidjson-devel),
|
||||
[project page](https://build.opensuse.org/project/show/home:mkittler)
|
||||
* Git master: [download page](https://software.opensuse.org/download.html?project=home:mkittler:vcs&package=reflective-rapidjson-devel),
|
||||
[project page](https://build.opensuse.org/project/show/home:mkittler:vcs)
|
||||
* Windows
|
||||
* for mingw-w64 PKGBUILDs checkout [my GitHub repository](https://github.com/Martchus/PKGBUILDs)
|
||||
|
||||
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-2024 Marius Kittler
|
||||
|
||||
All code is licensed under [GPL-2-or-later](LICENSE).
|
||||
|
|
6
TODOs.md
6
TODOs.md
|
@ -7,14 +7,14 @@
|
|||
- [x] Add documentation (install instructions, usage)
|
||||
- [x] Allow making 3rdparty classes/structs reflectable
|
||||
- [x] Add additional parameter for code generator to allow specifying relevant classes
|
||||
explicitely
|
||||
explicitly
|
||||
- [x] Fix traits currently relying on `JsonSerializable` being base class
|
||||
- [x] Allow exporting symbols
|
||||
- [x] Fix the massive number of warnings which are currently being created by the code generator (missing `-resource-dir` was the problem)
|
||||
- [ ] Test with libc++ (currently only tested with libstdc++)
|
||||
- [ ] Support templated classes
|
||||
- [ ] Allow (de)serialization of static members (if that makes sense?)
|
||||
- [ ] Allow ignoring particular members or selecting specificly which member variables should be considered
|
||||
- [ ] Allow ignoring particular members or selecting specifically which member variables should be considered
|
||||
* This could work similar to Qt's Signals & Slots macros.
|
||||
* but there should also be a way to do this for 3rdparty types.
|
||||
* Note that currently, *all* public member variables are (de)serialized.
|
||||
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,17 +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";
|
||||
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
|
||||
if (writePrivateMembers || field->getAccess() == clang::AS_public) {
|
||||
os << " serializer.write(customObject." << field->getName() << ");\n";
|
||||
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;
|
||||
}
|
||||
mt.concludeCondition(os);
|
||||
if (relevantBases.empty() && !mt.membersWritten) {
|
||||
os << " (void)serializer;\n (void)customObject;\n \n(void)version;";
|
||||
}
|
||||
os << "}\n";
|
||||
|
||||
|
@ -138,23 +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";
|
||||
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";
|
||||
os << " deserializer.read(customObject." << field->getName() << ", version);\n";
|
||||
mt.membersWritten = true;
|
||||
}
|
||||
}
|
||||
mt.concludeCondition(os);
|
||||
if (relevantBases.empty() && !mt.membersWritten) {
|
||||
os << " (void)deserializer;\n (void)customObject;\n";
|
||||
}
|
||||
os << " return version;\n";
|
||||
os << "}\n\n";
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
#include "./clangversionabstraction.h"
|
||||
#include "./frontendaction.h"
|
||||
|
||||
#include "resources/config.h"
|
||||
|
||||
#include <clang/Basic/FileManager.h>
|
||||
#include <clang/Frontend/FrontendActions.h>
|
||||
#include <clang/Tooling/Tooling.h>
|
||||
|
@ -30,8 +32,8 @@ CodeFactory::ToolInvocation::ToolInvocation(CodeFactory &factory)
|
|||
* \brief Constructs a new instance.
|
||||
* \remarks The specified arguments are not copied and must remain valid for the live-time of the code factory.
|
||||
*/
|
||||
CodeFactory::CodeFactory(
|
||||
const char *applicationPath, const std::vector<const char *> &sourceFiles, const std::vector<string> &clangOptions, std::ostream &os)
|
||||
CodeFactory::CodeFactory(std::string_view applicationPath, const std::vector<const char *> &sourceFiles,
|
||||
const std::vector<std::string_view> &clangOptions, std::ostream &os)
|
||||
: m_applicationPath(applicationPath)
|
||||
, m_sourceFiles(sourceFiles)
|
||||
, m_clangOptions(clangOptions)
|
||||
|
@ -50,8 +52,8 @@ CodeFactory::~CodeFactory()
|
|||
*/
|
||||
std::vector<string> CodeFactory::makeClangArgs() const
|
||||
{
|
||||
static const initializer_list<const char *> flags
|
||||
= { m_applicationPath, "-x", "c++", "-Wno-pragma-once-outside-header", "-std=c++14", "-fsyntax-only" };
|
||||
static const initializer_list<std::string_view> flags
|
||||
= { m_applicationPath, "-x", "c++", "-Wno-pragma-once-outside-header", "-std=c++14", "-fsyntax-only", "-D" PROJECT_VARNAME_UPPER };
|
||||
vector<string> clangArgs;
|
||||
clangArgs.reserve(flags.size() + m_clangOptions.size() + m_sourceFiles.size());
|
||||
clangArgs.insert(clangArgs.end(), flags.begin(), flags.end());
|
||||
|
@ -83,7 +85,7 @@ bool CodeFactory::generate() const
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Reads (relevent) AST elements using Clang and generates code.
|
||||
* \brief Reads (relevant) AST elements using Clang and generates code.
|
||||
*/
|
||||
bool CodeFactory::run()
|
||||
{
|
||||
|
|
|
@ -29,13 +29,13 @@ class CodeFactory {
|
|||
friend class Visitor;
|
||||
|
||||
public:
|
||||
CodeFactory(
|
||||
const char *applicationPath, const std::vector<const char *> &sourceFiles, const std::vector<std::string> &clangOptions, std::ostream &os);
|
||||
CodeFactory(std::string_view applicationPath, const std::vector<const char *> &sourceFiles, const std::vector<std::string_view> &clangOptions,
|
||||
std::ostream &os);
|
||||
~CodeFactory();
|
||||
|
||||
const std::vector<std::unique_ptr<CodeGenerator>> &generators() const;
|
||||
template <typename GeneratorType, typename... Args> void addGenerator(Args &&... args);
|
||||
template <typename GeneratorType, typename... Args> auto bindGenerator(Args &&... args);
|
||||
template <typename GeneratorType, typename... Args> void addGenerator(Args &&...args);
|
||||
template <typename GeneratorType, typename... Args> auto bindGenerator(Args &&...args);
|
||||
|
||||
bool run();
|
||||
clang::CompilerInstance *compilerInstance();
|
||||
|
@ -50,9 +50,9 @@ private:
|
|||
bool generate() const;
|
||||
std::vector<std::string> makeClangArgs() const;
|
||||
|
||||
const char *const m_applicationPath;
|
||||
std::string_view m_applicationPath;
|
||||
const std::vector<const char *> &m_sourceFiles;
|
||||
const std::vector<std::string> &m_clangOptions;
|
||||
const std::vector<std::string_view> &m_clangOptions;
|
||||
std::ostream &m_os;
|
||||
std::vector<std::unique_ptr<CodeGenerator>> m_generators;
|
||||
std::unique_ptr<ToolInvocation> m_toolInvocation;
|
||||
|
@ -64,7 +64,7 @@ private:
|
|||
* \brief Instantiates a code generator of the specified type and adds it to the current instance.
|
||||
* \remarks The specified \a args are forwarded to the generator's constructor.
|
||||
*/
|
||||
template <typename GeneratorType, typename... Args> void CodeFactory::addGenerator(Args &&... args)
|
||||
template <typename GeneratorType, typename... Args> void CodeFactory::addGenerator(Args &&...args)
|
||||
{
|
||||
m_generators.emplace_back(std::make_unique<GeneratorType>(*this, std::forward<Args>(args)...));
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ template <typename T> T &&wrapReferences(T &&val)
|
|||
* - The specified \a args are forwarded to the generator's constructor.
|
||||
* - No copy of \a args passed by reference is made.
|
||||
*/
|
||||
template <typename GeneratorType, typename... Args> auto CodeFactory::bindGenerator(Args &&... args)
|
||||
template <typename GeneratorType, typename... Args> auto CodeFactory::bindGenerator(Args &&...args)
|
||||
{
|
||||
return std::bind(&CodeFactory::addGenerator<GeneratorType, Args...>, this, Detail::wrapReferences(std::forward<Args>(args)...));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -124,11 +119,16 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
|
|||
os << " push(static_cast<const ::" << baseClass->qualifiedName << " &>(reflectable), value, allocator);\n";
|
||||
}
|
||||
os << " // push members\n";
|
||||
auto pushWritten = false;
|
||||
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
|
||||
if (pushPrivateMembers || field->getAccess() == clang::AS_public) {
|
||||
os << " push(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, allocator);\n";
|
||||
pushWritten = true;
|
||||
}
|
||||
}
|
||||
if (relevantBases.empty() && !pushWritten) {
|
||||
os << " (void)reflectable;\n (void)value;\n (void)allocator;\n";
|
||||
}
|
||||
os << "}\n";
|
||||
|
||||
// skip printing the pull method for classes without default constructor because deserializing those is currently not supported
|
||||
|
@ -146,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 = \""
|
||||
|
@ -154,6 +154,7 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
|
|||
<< "\";\n"
|
||||
" }\n"
|
||||
" // pull members\n";
|
||||
auto pullWritten = false;
|
||||
for (const clang::FieldDecl *field : relevantClass.record->fields()) {
|
||||
// skip const members
|
||||
if (field->getType().isConstant(field->getASTContext())) {
|
||||
|
@ -161,8 +162,12 @@ void JsonSerializationCodeGenerator::generate(ostream &os) const
|
|||
}
|
||||
if (pullPrivateMembers || field->getAccess() == clang::AS_public) {
|
||||
os << " pull(reflectable." << field->getName() << ", \"" << field->getName() << "\", value, errors);\n";
|
||||
pullWritten = true;
|
||||
}
|
||||
}
|
||||
if (relevantBases.empty() && !pullWritten) {
|
||||
os << " (void)reflectable;\n (void)value;\n";
|
||||
}
|
||||
os << " // restore error context for previous record\n"
|
||||
" if (errors) {\n"
|
||||
" errors->currentRecord = previousRecord;\n"
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -51,7 +51,7 @@ int main(int argc, char *argv[])
|
|||
parser.setMainArguments({ &generateArg, &noColorArg, &helpArg });
|
||||
|
||||
// parse arguments
|
||||
parser.parseArgs(argc, argv, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
|
||||
parser.parseArgs(argc, argv);
|
||||
if (helpArg.isPresent() || !generateArg.isPresent()) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -69,14 +69,16 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
|
||||
// compose options passed to the clang tool invocation
|
||||
vector<string> clangOptions;
|
||||
auto clangOptions = std::vector<std::string_view>();
|
||||
if (clangOptionsArg.isPresent()) {
|
||||
// add additional options specified via CLI argument
|
||||
for (const auto *const value : clangOptionsArg.values(0)) {
|
||||
// split options by ";" - not nice but this eases using CMake generator expressions
|
||||
const auto splittedValues(splitString<vector<string>>(value, ";", EmptyPartsTreat::Omit));
|
||||
const auto splittedValues = splitStringSimple<std::vector<std::string_view>>(value, ";");
|
||||
for (const auto &splittedValue : splittedValues) {
|
||||
clangOptions.emplace_back(move(splittedValue));
|
||||
if (!splittedValue.empty()) {
|
||||
clangOptions.emplace_back(splittedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +90,7 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
|
||||
// instantiate the code factory and add generators to it
|
||||
CodeFactory factory(parser.executable(), inputFileArg.values(0), clangOptions, *os);
|
||||
auto factory = CodeFactory(parser.executable(), inputFileArg.values(0), clangOptions, *os);
|
||||
factory.setErrorResilient(errorResilientArg.isPresent());
|
||||
// add specified generators if the --generator argument is present; otherwise add default generators
|
||||
if (generatorsArg.isPresent()) {
|
||||
|
@ -121,14 +123,14 @@ int main(int argc, char *argv[])
|
|||
|
||||
// read AST elements from input files and run the code generator
|
||||
if (!factory.run()) {
|
||||
cerr << Phrases::Error << "Errors occured." << Phrases::EndFlush;
|
||||
cerr << Phrases::Error << "Errors occurred." << Phrases::EndFlush;
|
||||
return -2;
|
||||
}
|
||||
|
||||
} catch (const std::ios_base::failure &failure) {
|
||||
const char *errorMessage = failure.what();
|
||||
if (os) {
|
||||
errorMessage = os->fail() || os->bad() ? "An IO error occured when writing to the output stream." : "An IO error occured.";
|
||||
errorMessage = os->fail() || os->bad() ? "An IO error occurred when writing to the output stream." : "An IO error occurred.";
|
||||
} else {
|
||||
errorMessage = "An IO error when opening output stream.";
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -64,13 +64,14 @@ JsonGeneratorTests::JsonGeneratorTests()
|
|||
*/
|
||||
void JsonGeneratorTests::testGeneratorItself()
|
||||
{
|
||||
const string inputFilePath(testFilePath("some_structs.h"));
|
||||
const vector<const char *> inputFiles{ inputFilePath.data() };
|
||||
const vector<string> clangOptions{ "-resource-dir", REFLECTION_GENERATOR_CLANG_RESOURCE_DIR, "-std=c++17", "-I", CPP_UTILITIES_INCLUDE_DIRS,
|
||||
const auto inputFilePath = testFilePath("some_structs.h");
|
||||
const auto inputFiles = vector<const char *>{ inputFilePath.data() };
|
||||
const auto clangOptions
|
||||
= vector<std::string_view>{ "-resource-dir", REFLECTION_GENERATOR_CLANG_RESOURCE_DIR, "-std=c++17", "-I", CPP_UTILITIES_INCLUDE_DIRS,
|
||||
#ifdef RAPIDJSON_INCLUDE_DIRS
|
||||
"-I", RAPIDJSON_INCLUDE_DIRS
|
||||
"-I", RAPIDJSON_INCLUDE_DIRS
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
stringstream buffer;
|
||||
JsonSerializationCodeGenerator::Options jsonOptions;
|
||||
|
@ -83,7 +84,7 @@ void JsonGeneratorTests::testGeneratorItself()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests the generator CLI explicitely.
|
||||
* \brief Tests the generator CLI explicitly.
|
||||
* \remarks Only available under UNIX (like) systems so far, because TESTUTILS_ASSERT_EXEC has not been implemented
|
||||
* for other platforms.
|
||||
*/
|
||||
|
@ -163,7 +164,7 @@ void JsonGeneratorTests::testNesting()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Like testIncludingGeneratedHeader() but also tests single inheritence.
|
||||
* \brief Like testIncludingGeneratedHeader() but also tests single inheritance.
|
||||
*/
|
||||
void JsonGeneratorTests::testSingleInheritence()
|
||||
{
|
||||
|
@ -192,7 +193,7 @@ void JsonGeneratorTests::testSingleInheritence()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Like testIncludingGeneratedHeader() but also tests multiple inheritence.
|
||||
* \brief Like testIncludingGeneratedHeader() but also tests multiple inheritance.
|
||||
*/
|
||||
void JsonGeneratorTests::testMultipleInheritence()
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -128,7 +128,7 @@ REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(NotJsonSerializable);
|
|||
REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(NestedNotJsonSerializable);
|
||||
|
||||
/*!
|
||||
* \brief The OtherNotJsonSerializable struct is used to test whether code for (de)serialization is generated for classes explicitely
|
||||
* \brief The OtherNotJsonSerializable struct is used to test whether code for (de)serialization is generated for classes explicitly
|
||||
* specified via CMake macro (despite use of REFLECTIVE_RAPIDJSON_ADAPT_JSON_SERIALIZABLE or JsonSerializable is
|
||||
* missing).
|
||||
*/
|
||||
|
|
|
@ -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)
|
||||
|
@ -33,13 +33,7 @@ list(APPEND HEADER_FILES binary/reflector.h binary/reflector-boosthana.h binary/
|
|||
list(APPEND TEST_SRC_FILES tests/traits.cpp tests/binaryreflector.cpp tests/binaryreflector-boosthana.cpp)
|
||||
|
||||
# add (only) the CMake module and include dirs for c++utilities because we're not depending on the actual library
|
||||
list(APPEND CMAKE_MODULE_PATH ${CPP_UTILITIES_MODULE_DIRS})
|
||||
if (CPP_UTILITIES_SOURCE_DIR)
|
||||
list(APPEND PUBLIC_INCLUDE_DIRS $<BUILD_INTERFACE:${CPP_UTILITIES_SOURCE_DIR}/..>
|
||||
$<INSTALL_INTERFACE:${CPP_UTILITIES_INCLUDE_DIRS}>)
|
||||
else ()
|
||||
list(APPEND PUBLIC_INCLUDE_DIRS "${CPP_UTILITIES_INCLUDE_DIRS}")
|
||||
endif ()
|
||||
use_cpp_utilities(ONLY_HEADERS VISIBILITY PUBLIC)
|
||||
|
||||
# find RapidJSON, also add only the include dirs because RapidJSON is a header-only library
|
||||
if (RapidJSON_FOUND)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -128,12 +150,12 @@ template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::
|
|||
|
||||
const auto id = (mode & 0x4) ? readUInt64BE() : readVariableLengthUIntBE(); // the 3rd bit being flagged indicates a big ID
|
||||
if ((mode & 0x3) == 1) {
|
||||
// first occurence: make a new pointer
|
||||
// first occurrence: make a new pointer
|
||||
m_pointer[id] = pointer = std::make_shared<typename Type::element_type>();
|
||||
read(*pointer);
|
||||
return;
|
||||
}
|
||||
// further occurences: copy previous pointer
|
||||
// further occurrences: copy previous pointer
|
||||
try {
|
||||
pointer = std::any_cast<Type>(m_pointer[id]);
|
||||
} catch (const std::bad_any_cast &) {
|
||||
|
@ -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
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
/*!
|
||||
* \file serializable.h
|
||||
* \brief Contains only the definiation of the BinarySerializable template class which makes the reflection
|
||||
* \brief Contains only the definition of the BinarySerializable template class which makes the reflection
|
||||
* accessible. The actual implementation is found in binaryreflector.h and generated files.
|
||||
*/
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../../versioning.h
|
|
@ -110,7 +110,7 @@ struct JsonDeserializationError {
|
|||
JsonDeserializationError(JsonDeserializationErrorKind kind, JsonType expectedType, JsonType actualType, const char *record,
|
||||
const char *member = nullptr, std::size_t index = noIndex);
|
||||
|
||||
/// \brief Which kind of error occured.
|
||||
/// \brief Which kind of error occurred.
|
||||
JsonDeserializationErrorKind kind;
|
||||
/// \brief The expected type (might not be relevant for all error kinds).
|
||||
JsonType expectedType;
|
||||
|
@ -123,7 +123,7 @@ struct JsonDeserializationError {
|
|||
/// \brief The index in the array which was being processed when the error was ascertained.
|
||||
std::size_t index;
|
||||
|
||||
/// \brief Indicates no array was being processed when the error occured.
|
||||
/// \brief Indicates no array was being processed when the error occurred.
|
||||
static constexpr std::size_t noIndex = std::numeric_limits<std::size_t>::max();
|
||||
};
|
||||
|
||||
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
@ -177,7 +190,7 @@ inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified C-string to the specified value.
|
||||
* \brief Pushes the specified std::string_view to the specified value.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string_view>> * = nullptr>
|
||||
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
|
@ -206,7 +219,7 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
|
|||
{
|
||||
value.SetArray();
|
||||
RAPIDJSON_NAMESPACE::Value::Array array(value.GetArray());
|
||||
array.Reserve(reflectable.size(), allocator);
|
||||
array.Reserve(rapidJsonSize(reflectable.size()), allocator);
|
||||
for (const auto &item : reflectable) {
|
||||
push(item, array, allocator);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -332,7 +344,7 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
|
|||
}
|
||||
|
||||
RAPIDJSON_NAMESPACE::Value index, data;
|
||||
index.SetInt(reflectable.index());
|
||||
index.SetUint64(reflectable.index());
|
||||
std::visit(
|
||||
[&data, &allocator](const auto &reflectableOfActualType) {
|
||||
if constexpr (!std::is_same_v<std::decay_t<decltype(reflectableOfActualType)>, std::monostate>) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -745,9 +778,9 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
|
|||
continue;
|
||||
}
|
||||
const auto array = i->value.GetArray();
|
||||
for (const auto &value : array) {
|
||||
for (const auto &arrayValue : array) {
|
||||
auto insertedIterator = reflectable.insert(typename Type::value_type(i->name.GetString(), typename Type::mapped_type()));
|
||||
pull(insertedIterator->second, value, errors);
|
||||
pull(insertedIterator->second, arrayValue, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -899,20 +946,14 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
|
|||
return;
|
||||
}
|
||||
const auto &indexValue = indexIterator->value;
|
||||
if (!indexValue.IsInt()) {
|
||||
if (!indexValue.IsUint64()) {
|
||||
if (errors) {
|
||||
errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantIndex, JsonType::Number, jsonType(indexValue.GetType()),
|
||||
errors->currentRecord, errors->currentMember, errors->currentIndex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto index = indexValue.GetInt();
|
||||
if (index < 0) {
|
||||
errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantIndex, JsonType::Number, JsonType::Number, errors->currentRecord,
|
||||
errors->currentMember, errors->currentIndex);
|
||||
return;
|
||||
}
|
||||
Detail::assignVariantValueByRuntimeIndex(static_cast<std::size_t>(index), reflectable, dataIterator->value, errors);
|
||||
Detail::assignVariantValueByRuntimeIndex(indexValue.GetUint64(), reflectable, dataIterator->value, errors);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -941,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;
|
||||
|
@ -1018,7 +1059,7 @@ RAPIDJSON_NAMESPACE::Document toJsonDocument(const char *reflectable)
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Serializes the specified \a reflectable which is a C-string.
|
||||
* \brief Serializes the specified \a reflectable which is an std::string_view.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string_view>> * = nullptr>
|
||||
RAPIDJSON_NAMESPACE::Document toJsonDocument(std::string_view reflectable)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
/*!
|
||||
* \file serializable.h
|
||||
* \brief Contains only the definiation of the JsonSerializable template class which makes the reflection
|
||||
* \brief Contains only the definition of the JsonSerializable template class which makes the reflection
|
||||
* accessible. The actual implementation is found in jsonreflector.h and generated files.
|
||||
*/
|
||||
|
||||
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -160,11 +160,11 @@ void JsonReflectorBoostHanaTests::testDeserializeNestedObjects()
|
|||
CPPUNIT_ASSERT_EQUAL(2_st, testObjects.size());
|
||||
CPPUNIT_ASSERT_EQUAL(42, testObjects[0].number);
|
||||
CPPUNIT_ASSERT_EQUAL(43, testObjects[1].number);
|
||||
for (const TestObjectHana &testObj : testObjects) {
|
||||
CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2);
|
||||
CPPUNIT_ASSERT_EQUAL(vector<int>({ 1, 2, 3, 4 }), testObj.numbers);
|
||||
CPPUNIT_ASSERT_EQUAL("test"s, testObj.text);
|
||||
CPPUNIT_ASSERT_EQUAL(false, testObj.boolean);
|
||||
for (const TestObjectHana &nestedTestObj : testObjects) {
|
||||
CPPUNIT_ASSERT_EQUAL(3.141592653589793, nestedTestObj.number2);
|
||||
CPPUNIT_ASSERT_EQUAL(vector<int>({ 1, 2, 3, 4 }), nestedTestObj.numbers);
|
||||
CPPUNIT_ASSERT_EQUAL("test"s, nestedTestObj.text);
|
||||
CPPUNIT_ASSERT_EQUAL(false, nestedTestObj.boolean);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
@ -237,7 +241,7 @@ void JsonReflectorTests::testSerializePrimitives()
|
|||
Document::Array array(doc.GetArray());
|
||||
|
||||
// string
|
||||
const string foo("foo"); // musn't be destroyed until JSON is actually written
|
||||
const string foo("foo"); // mustn't be destroyed until JSON is actually written
|
||||
JsonReflector::push<string>(foo, array, alloc);
|
||||
JsonReflector::push<const char *>("bar", array, alloc);
|
||||
// number
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -500,11 +526,11 @@ void JsonReflectorTests::testDeserializeNestedObjects()
|
|||
CPPUNIT_ASSERT_EQUAL(2_st, testObjects.size());
|
||||
CPPUNIT_ASSERT_EQUAL(42, testObjects[0].number);
|
||||
CPPUNIT_ASSERT_EQUAL(43, testObjects[1].number);
|
||||
for (const TestObject &testObj : testObjects) {
|
||||
CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2);
|
||||
CPPUNIT_ASSERT_EQUAL(vector<int>({ 1, 2, 3, 4 }), testObj.numbers);
|
||||
CPPUNIT_ASSERT_EQUAL("test"s, testObj.text);
|
||||
CPPUNIT_ASSERT_EQUAL(false, testObj.boolean);
|
||||
for (const TestObject &nestedTestObj : testObjects) {
|
||||
CPPUNIT_ASSERT_EQUAL(3.141592653589793, nestedTestObj.number2);
|
||||
CPPUNIT_ASSERT_EQUAL(vector<int>({ 1, 2, 3, 4 }), nestedTestObj.numbers);
|
||||
CPPUNIT_ASSERT_EQUAL("test"s, nestedTestObj.text);
|
||||
CPPUNIT_ASSERT_EQUAL(false, nestedTestObj.boolean);
|
||||
}
|
||||
|
||||
const auto nestedInVector(JsonReflector::fromJson<vector<TestObject>>(
|
||||
|
@ -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().
|
||||
*/
|
||||
|
|
|
@ -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");
|
||||
|
|
24
lib/traits.h
24
lib/traits.h
|
@ -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>
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue