Compare commits

...

65 Commits

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

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

It should be fine because if the base class is versioned it won't make a
difference but if it is versioned the version from the derived class can be
utilized.
2022-05-15 21:00:02 +02:00
Martchus 9c3bf01c8f Avoid warnings about unused var `allocator` for empty classes 2022-05-15 20:52:30 +02:00
Martchus 1e3417f8d0 Fix (de)serialization of std::int8_t/std::uint8_t
Those types are not supported by RapidJSON and therefore need extra
handling.
2022-05-15 03:11:39 +02:00
Martchus a6b9d771aa Do not treat warning "'this' pointer is null" appearing with GCC 12 as error
It is about code included from libclang and likely wrong so let's just not
abort the compilation due to it.
2022-05-12 20:23:33 +02:00
Martchus 762540f5e5 Add support for `std::optional` 2022-05-07 18:40:37 +02:00
Martchus 0633923935 Simplify use of type traits in `json/reflector.h` 2022-05-07 16:52:32 +02:00
Martchus 11491b1387 Add stalebot config 2022-04-12 01:05:03 +02:00
Martchus df787f3105 Add copyright notice 2022-04-05 20:23:32 +02:00
Martchus 2cc044c705 Adapt tests to efaa8a8 2022-03-28 17:06:28 +02:00
Martchus 4966625d8b Clarify that license is "GPL-2-or-later" 2022-03-15 21:46:56 +01:00
Martchus efaa8a8441 Avoid warnings about using uninitialized variable 2022-02-22 20:03:49 +01:00
Martchus 22611457f9 Fix plural of pointer in README.md 2022-02-05 22:28:20 +01:00
Martchus 59ff3c19eb Allow reporting a type mismatch specifying the expected RapidJSON type directly 2022-01-03 23:24:40 +01:00
Martchus f8f551a78a Fix include and namespace-prefix for using `std::move` in JSON reflector 2021-12-05 23:34:39 +01:00
Martchus 8b66ca3e6b Avoid using `DeclBase::getLangOpts()` to support older Clang versions
The `DeclBase::getLangOpts()` is merely a convenience but only available
in Clang 11 or newer.
2021-09-19 01:17:30 +02:00
Martchus 44c6b8c609 Throw exception during binary deserialization when version is not supported 2021-07-25 19:19:02 +02:00
Martchus 13428667f8 Remove surplus space 2021-07-25 19:13:29 +02:00
Martchus 852dfb7e3c Add symlink for versioning 2021-07-25 17:31:07 +02:00
Martchus 0a902ac30c Add experimental versioning for binary (de)serializer 2021-07-13 00:37:05 +02:00
Martchus 5e72012ed5 Fix typos found via `codespell --skip .git -w` 2021-07-03 19:50:25 +02:00
Martchus a4dd52acfa Define REFLECTIVE_RAPIDJSON_GENERATOR macro during generator runs
So one can distinguish regular compilation from the generator run by
checking this macro.
2021-05-16 19:25:36 +02:00
Martchus e3d32ddfa1 Use std::string_view for CodeFactory parameters (where possible) 2021-05-16 19:25:30 +02:00
Martchus 8f1909dfdf Exit when parsing arguments fails 2021-05-13 16:56:27 +02:00
Martchus 874c964e0b Prevent warnings about unused variables in generated code 2021-03-22 14:16:40 +01:00
Martchus 30735ba187 Fix warnings 2021-03-20 21:25:56 +01:00
Martchus 5110cff5eb Fix documentation for std::string_view's push/toJsonDocument function 2021-01-15 17:48:46 +01:00
Martchus dc7c74c497 Update minimum C++ version mentioned in README
This likely doesn't work anymore with C++14 and I won't put any effort into
C++14 compatibility anymore at this point.
2021-01-15 15:27:28 +01:00
Martchus 80183f5269 Remove list of supported versions as it is too much work to maintain 2021-01-15 15:24:50 +01:00
Martchus 5c49a438ad Use header-only target of c++utilities
The current approach even distinguished between the build and install
interface. However, CPP_UTILITIES_INCLUDE_DIRS is not correctly evaluated
at the time PUBLIC_INCLUDE_DIRS is populated here because the variable
PACKAGE_PREFIX_DIR from the c++utilities config module still points to the
build directory at build time and using $<INSTALL_INTERFACE:…> can not help
with that.
2021-01-01 18:48:14 +01:00
Martchus 6252a7335a Apply cmake-format and clang-format 2020-12-05 21:29:55 +01:00
Martchus 2b6634d574 Allow supplying RapidJSON from outer scope 2020-12-05 21:29:38 +01:00
Martchus 0010e32515 Improve download section of README 2020-12-05 21:28:22 +01:00
Martchus 1970145b90 Add documentation regarding direct RapidJSON usage 2020-06-29 20:59:01 +02:00
Martchus 1e95c3d1ca Don't use StringRef with RapidJSON's SetString and allocator
There is no overload taking a RAPIDJSON_NAMESPACE::StringRef and an
allocator. So the StringRef will be implicitly converted back to a plain
CharType pointer causing an additional length calculation.
2020-06-29 20:26:19 +02:00
Martchus c33e1687cb Support serializing std::string_view 2020-06-26 22:08:05 +02:00
Martchus 4ff49156da Apply clang-format 2020-05-28 12:02:22 +02:00
Martchus 18716b17be
Merge pull request #3 from otreblan/master
Fix missing header in gcc10
2020-05-28 11:51:42 +02:00
otreblan beb4491b18
Fix missing header in gcc10 2020-05-27 22:20:22 -05:00
Martchus 8c032ee7a6 Fix compatibility with LLVM/Clang 10 2020-04-12 17:22:37 +02:00
Martchus c4f622df8d Apply cmake-format 2020-03-08 13:52:01 +01:00
Martchus 442359f5bf Add a function to format JSON deserialization errors 2020-02-29 22:35:02 +01:00
Martchus 86481fa459 Prevent CMake error when using CMake < 3.15.0 2020-02-18 19:40:48 +01:00
Martchus cf41f4596a Prevent passing "empty" flags to the code generator
Otherwise flags like "-D -I/foo" might be passed to clang which
will then complain that "-I/foo" is not a valid name for a macro.
2020-02-14 16:26:50 +01:00
Martchus 4b4d674f56 Allow adding clang options from dependencies 2020-01-31 20:43:01 +01:00
Martchus 891b96a38f Fix build with custom target suffix 2020-01-31 19:49:18 +01:00
Martchus c68d9ea384 Format CMake files with cmake-format 0.6.7 2020-01-26 20:42:03 +01:00
Martchus e7bbdd0af6 Allow logging the exact list of options passed to clang
Since the options get splitted and not just passed as-is
this can be useful for debugging.
2020-01-26 20:41:34 +01:00
Martchus ed3f89953f Add missing symlinks to include directory 2020-01-26 20:38:47 +01:00
Martchus 6e4077eed2 Add JsonDeserializationErrors::ThrowOn::All 2020-01-26 20:38:24 +01:00
Martchus 2f1098ae3d Make m_pointer of BinarySerializer and BinaryDeserializer private 2020-01-12 00:50:30 +01:00
Martchus b37f467dcd Fix name of CMake package in example 2020-01-12 00:33:02 +01:00
Martchus 7482c64931 Support --binary-visibility in CMake function 2020-01-11 14:54:14 +01:00
40 changed files with 1135 additions and 322 deletions

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

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

View File

@ -1,8 +1,8 @@
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# set metadata
project(reflective_rapidjson)
set(META_PROJECT_NAME ${PROJECT_NAME})
set(META_PROJECT_NAME reflective_rapidjson)
set(META_APP_NAME "Reflection for RapidJSON")
set(META_APP_AUTHOR "Martchus")
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/reflective-rapidjson")
@ -11,14 +11,11 @@ set(META_APP_CATEGORIES "Utility;")
set(META_GUI_OPTIONAL false)
set(META_VERSION_MAJOR 0)
set(META_VERSION_MINOR 0)
set(META_VERSION_PATCH 11)
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)
# set project name for IDEs like Qt Creator
project(${META_PROJECT_NAME})
# ensure testing is enabled at this level (and not only for particular sub directories)
enable_testing()
@ -49,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()

113
README.md
View File

@ -57,22 +57,24 @@ The following table shows the mapping of supported C++ types to supported JSON t
| `float` and `double` | number |
| `enum` and `enum class` | number |
| `std::string` | string |
| `const char *` | string |
| `std::string_view` | string/null |
| `const char *` | string/null |
| 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 *` strings are only supported for serialization.
* 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
@ -97,6 +99,7 @@ The following table shows the mapping of supported C++ types to supported JSON t
* The binary (de)serializer supports approximately the same C++ types but obviously maps them to a platform
independent binary representation rather than a JSON type.
## Usage
This example shows how the library can be used to make a `struct` serializable:
<pre>
@ -142,6 +145,22 @@ There are further arguments available, see:
reflective_rapidjson_generator --help
</pre>
### Mixing with direct RapidJSON usage and further notes
It is of course possible to mix automatic serialization/deserialization with direct RapidJSON usage. This can be
done by invoking the `push` and `pull` functions within the `ReflectiveRapidJSON::JsonReflector` namespace directly.
The `push` functions are used on serialization to populate intermediate data structures for the serializer of the
RapidJSON library. The intermediate JSON document can also easily be obtained via
`JsonSerializable<Type>::toJsonDocument()`.
Note that this means a copy of the provided data will be made. That includes all strings as well. Currently there
is no way to use RapidJSON's copy-free `SetString`-overloads instead. As a consequence the mentioned intermediate
JSON document can be serialized without causing any further read accesses to the actual data structures.
The `pull` functions are used to populate your data structures from intermediate data structures produced by the
parser of RapidJSON. Also in this case a copy will be made so only owning data structures can be used when
deserializing (see remarks regarding supported datatypes).
#### Binary (de)serialization
It works very similar to the example above. Just use the `BinarySerializable` class instead (or in addition):
@ -154,7 +173,7 @@ struct TestObject : public ReflectiveRapidJSON::BinarySerializable&lt;TestObject
It is possible to use the provided CMake macro to automate the code generator invocation:
<pre>
# find the package and make macro available
find_package(reflective-rapidjson REQUIRED)
find_package(reflective_rapidjson REQUIRED)
list(APPEND CMAKE_MODULE_PATH ${REFLECTIVE_RAPIDJSON_MODULE_DIRS})
include(ReflectionGenerator)
@ -209,8 +228,7 @@ To adjust the default location, just add eg. `--clang-opt -resource-dir /usr/lib
It makes most sense to specify the same options for the code generator as during the actual compilation. This way the code
generator uses the same flags, defines and include directories as the compiler and hence behaves like the compiler.
When using the CMake macro, it is possible to automatically pass all compile flags, compile definitions and include directories
from certain targets to the code generator. Those targets can be specified using the
Macro's `CLANG_OPTIONS_FROM_TARGETS` argument.
from certain targets to the code generator. Those targets can be specified using the macro's `CLANG_OPTIONS_FROM_TARGETS` argument.
#### Notes regarding cross-compilation
* For cross compilation, it is required to build the code generator for the platform you're building on.
@ -336,6 +354,54 @@ An example for such custom (de)serialization can be found in the file
`json/reflector-chronoutilities.h`. It provides (de)serialization of `DateTime` and
`TimeSpan` objects from the C++ utilities library mentioned under dependencies.
### Versioning
#### JSON (de)serializer
The JSON (de)serializer doesn't support versioning at this point. It'll simply read/write the
members present in the struct. Additional members (which were e.g. present in older/newer
versions of the struct) are ignored when reading and in consequence dropped when writing.
#### Binary (de)serializer
The binary (de)serializer supports *very* experimental versioning. Otherwise adding/removing
members is a breaking change. The versioning looks like this:
<pre>
// enable definition of the macros shown below (otherwise use long macros defined in
// `lib/versioning.h`)
#define REFLECTIVE_RAPIDJSON_SHORT_MACROS
#include &lt;reflective_rapidjson/binary/serializable.h&gt;
// example struct where version is *not* serialized/deserialized; defaults to version from
// outer scope when reading/writing, defaults to version 0 on top-level
struct Nested : public BinarySerializable&lt;Nested&gt; { //
std::uint32_t foo; // will be read/written in any case
as_of_version(3):
std::uint32_t bar; // will be read/written if outer scope version is &gt;= 3
};
// example struct where version is serialized/deserialized; defaults to version 3 when writing
struct Example : public BinarySerializable&lt;Example, 3&gt; {
Nested nested; // will be read/written in any case, version is "propagated down"
std::uint32_t a, b; // will be read/written in any case
until_version(2):
std::uint32_t c, d; // will be read/written if version is &lt;= 2
as_of_version(3):
std::uint32_t e, f; // will be read/written if version is &gt;= 3
as_of_version(4):
std::uint32_t g; // will be read/written if version is &gt;= 4
};
</pre>
The version specified as template argument is also assumed to be the highest supported version.
If a higher version is encountered during deserialization, `BinaryVersionNotSupported` is thrown
and the deserialization aborted.
Note that the versioning is mostly untested at this point.
### Remarks
* Static member variables and member functions are currently ignored by the generator.
* It is currently not possible to ignore a specific member variable.
@ -365,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)
@ -384,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.
@ -454,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"
@ -465,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.no-ip.biz/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-2023 Marius Kittler
All code is licensed under [GPL-2-or-later](LICENSE).

View File

@ -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)

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# metadata
set(META_PROJECT_NAME reflective_rapidjson_generator)
@ -45,19 +45,23 @@ if (TARGET clang-cpp)
list(APPEND PRIVATE_LIBRARIES clang-cpp LLVM)
else ()
list(
APPEND PRIVATE_LIBRARIES
clangTooling
clangFrontend
clangSerialization
clangSema
clangAST
clangLex
clangBasic
LLVM)
APPEND
PRIVATE_LIBRARIES
clangTooling
clangFrontend
clangSerialization
clangSema
clangAST
clangLex
clangBasic
LLVM)
endif ()
# also add reflective_rapidjson which is header-only but might pull additional include dirs for RapidJSON
list(APPEND PRIVATE_LIBRARIES reflective_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)
@ -68,7 +72,7 @@ include(ShellCompletion)
include(Doxygen)
# trigger code generator for tests because the tests already contain structs to be (de)serialized
if (TARGET reflective_rapidjson_generator_tests)
if (TARGET "${META_TARGET_NAME}_tests")
include(ReflectionGenerator)
# cmake-format: off
add_reflection_generator_invocation(
@ -84,7 +88,7 @@ if (TARGET reflective_rapidjson_generator_tests)
CLANG_OPTIONS
-std=c++17
CLANG_OPTIONS_FROM_TARGETS
reflective_rapidjson_generator_tests
"${META_TARGET_NAME}_tests"
JSON_CLASSES
OtherNotJsonSerializable # test specifying classes for JSON (de)serialization manually
SomeOtherClassName # specifying a class that does not exist should not cause any problems
@ -93,7 +97,7 @@ if (TARGET reflective_rapidjson_generator_tests)
)
# cmake-format: on
list(APPEND TEST_HEADER_FILES ${TEST_GENERATED_HEADER_FILES})
target_sources(reflective_rapidjson_generator_tests PRIVATE ${TEST_GENERATED_HEADER_FILES})
target_sources("${META_TARGET_NAME}_tests" PRIVATE ${TEST_GENERATED_HEADER_FILES})
endif ()
# add paths for include dirs of c++utilities and RapidJSON to config header so test cases can use it

View File

@ -5,6 +5,10 @@
#include <clang/AST/DeclCXX.h>
#include <clang/AST/DeclFriend.h>
#include <clang/AST/DeclTemplate.h>
#include <clang/AST/Expr.h>
#include <clang/AST/RecursiveASTVisitor.h>
#include <llvm/ADT/APInt.h>
#include <iostream>
@ -35,36 +39,159 @@ BinarySerializationCodeGenerator::BinarySerializationCodeGenerator(CodeFactory &
}
/*!
* \brief Returns the qualified name of the specified \a record if it is considered relevant.
* \brief Checks whether \a possiblyRelevantClass is actually relevant.
*/
string BinarySerializationCodeGenerator::qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const
void BinarySerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
{
const string qualifiedName(record->getQualifiedNameAsString());
switch (isQualifiedNameIfRelevant(record, qualifiedName)) {
case IsRelevant::Yes:
return qualifiedName;
case IsRelevant::No:
return string();
default:;
SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
return;
}
// consider all classes specified via "--additional-classes" argument relevant
if (!m_options.additionalClassesArg.isPresent()) {
return string();
return;
}
for (const char *className : m_options.additionalClassesArg.values()) {
if (className == qualifiedName) {
return qualifiedName;
for (const char *const className : m_options.additionalClassesArg.values()) {
if (className == possiblyRelevantClass.qualifiedName) {
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
return;
}
}
}
return string();
/// \brief The RetrieveIntegerLiteralFromDeclaratorDecl struct is used to traverse a variable declaration to get the integer value.
struct RetrieveIntegerLiteralFromDeclaratorDecl : public clang::RecursiveASTVisitor<RetrieveIntegerLiteralFromDeclaratorDecl> {
explicit RetrieveIntegerLiteralFromDeclaratorDecl(const clang::ASTContext &ctx);
bool VisitStmt(clang::Stmt *st);
const clang::ASTContext &ctx;
std::uint64_t res;
bool success;
};
/// \brief Constructs a new instance for the specified AST context.
RetrieveIntegerLiteralFromDeclaratorDecl::RetrieveIntegerLiteralFromDeclaratorDecl(const clang::ASTContext &ctx)
: ctx(ctx)
, res(0)
, success(false)
{
}
/// \brief Reads the integer value of \a st for integer literals.
bool RetrieveIntegerLiteralFromDeclaratorDecl::VisitStmt(clang::Stmt *st)
{
if (st->getStmtClass() != clang::Stmt::IntegerLiteralClass) {
return true;
}
const auto *const integerLiteral = static_cast<const clang::IntegerLiteral *>(st);
auto evaluation = clang::Expr::EvalResult();
integerLiteral->EvaluateAsInt(evaluation, ctx, clang::Expr::SE_NoSideEffects, true);
if (!evaluation.Val.isInt()) {
return true;
}
const auto &asInt = evaluation.Val.getInt();
if (asInt.getActiveBits() > 64) {
return true;
}
res = asInt.getZExtValue();
success = true;
return false;
}
/// \brief The MemberTracking struct is an internal helper for BinarySerializationCodeGenerator::generate().
struct MemberTracking {
bool membersWritten = false, withinCondition = false;
BinaryVersion asOfVersion = BinaryVersion(), lastAsOfVersion = BinaryVersion();
BinaryVersion untilVersion = BinaryVersion(), lastUntilVersion = BinaryVersion();
bool checkForVersionMarker(clang::Decl *decl);
void concludeCondition(std::ostream &os);
void writeVersionCondition(std::ostream &os);
void writeExtraPadding(std::ostream &os);
};
/*!
* \brief Returns whether \a delc is a static member variable and processes special static member variables
* for versioning.
*/
bool MemberTracking::checkForVersionMarker(clang::Decl *decl)
{
if (decl->getKind() != clang::Decl::Kind::Var) {
return false;
}
auto *const declarator = static_cast<clang::DeclaratorDecl *>(decl);
const auto declarationName = declarator->getName();
const auto isAsOfVersion = declarationName.startswith("rrjAsOfVersion");
if (isAsOfVersion || declarationName.startswith("rrjUntilVersion")) {
auto v = RetrieveIntegerLiteralFromDeclaratorDecl(declarator->getASTContext());
v.TraverseDecl(declarator);
if (v.success) {
if (isAsOfVersion) {
asOfVersion = v.res;
if (asOfVersion > untilVersion) {
untilVersion = 0;
}
} else {
untilVersion = v.res;
if (untilVersion < asOfVersion) {
asOfVersion = 0;
}
}
}
}
return true;
}
/*!
* \brief Concludes an unfinished version condition if-block.
*/
void MemberTracking::concludeCondition(std::ostream &os)
{
if (withinCondition) {
os << " }\n";
}
}
/*!
* \brief Starts a new version condition if-block if versioning parameters have changed.
*/
void MemberTracking::writeVersionCondition(std::ostream &os)
{
if (asOfVersion == lastAsOfVersion && untilVersion == lastUntilVersion) {
return;
}
concludeCondition(os);
lastAsOfVersion = asOfVersion;
lastUntilVersion = untilVersion;
if ((withinCondition = asOfVersion || untilVersion)) {
os << " if (";
if (asOfVersion) {
os << "version >= " << asOfVersion;
if (untilVersion) {
os << " && ";
}
}
if (untilVersion) {
os << "version <= " << untilVersion;
}
os << ") {\n";
}
}
/*!
* \brief Writes extra padding (if within a version condition).
*/
void MemberTracking::writeExtraPadding(std::ostream &os)
{
if (withinCondition) {
os << " ";
}
}
/*!
* \brief Generates pull() and push() helper functions in the ReflectiveRapidJSON::BinaryReflector namespace for the relevant classes.
*/
void BinarySerializationCodeGenerator::generate(ostream &os) const
void BinarySerializationCodeGenerator::generate(std::ostream &os) const
{
// initialize source manager to make use of isOnlyIncluded() for skipping records which are only included
lazyInitializeSourceManager();
@ -118,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";
}

View File

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

View File

@ -1,6 +1,9 @@
#include "./codefactory.h"
#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>
@ -20,7 +23,7 @@ struct CodeFactory::ToolInvocation {
CodeFactory::ToolInvocation::ToolInvocation(CodeFactory &factory)
: fileManager({ "." })
, invocation(factory.makeClangArgs(), new FrontendAction(factory), &fileManager)
, invocation(factory.makeClangArgs(), maybe_unique(new FrontendAction(factory)), &fileManager)
{
fileManager.Retain();
}
@ -29,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)
@ -49,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());
@ -82,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()
{

View File

@ -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)...));
}

View File

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

View File

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

View File

@ -35,30 +35,25 @@ JsonSerializationCodeGenerator::JsonSerializationCodeGenerator(CodeFactory &fact
}
/*!
* \brief Returns the qualified name of the specified \a record if it is considered relevant.
* \brief Checks whether \a possiblyRelevantClass is actually relevant.
*/
string JsonSerializationCodeGenerator::qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const
void JsonSerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
{
const string qualifiedName(record->getQualifiedNameAsString());
switch (isQualifiedNameIfRelevant(record, qualifiedName)) {
case IsRelevant::Yes:
return qualifiedName;
case IsRelevant::No:
return string();
default:;
SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
return;
}
// consider all classes specified via "--additional-classes" argument relevant
if (!m_options.additionalClassesArg.isPresent()) {
return string();
return;
}
for (const char *className : m_options.additionalClassesArg.values()) {
if (className == qualifiedName) {
return qualifiedName;
for (const char *const className : m_options.additionalClassesArg.values()) {
if (className == possiblyRelevantClass.qualifiedName) {
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
return;
}
}
return string();
}
/*!
@ -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"

View File

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

View File

@ -39,10 +39,11 @@ int main(int argc, char *argv[])
generatorsArg.setCombinable(true);
ConfigValueArgument clangOptionsArg("clang-opt", '\0', "specifies arguments/options to be passed to Clang", { "option" });
clangOptionsArg.setRequiredValueCount(Argument::varValueCount);
ConfigValueArgument logClangOptions("log-clang-opt", '\0', "logs the options passed to Clang");
ConfigValueArgument errorResilientArg("error-resilient", '\0', "turns most errors into warnings");
HelpArgument helpArg(parser);
NoColorArgument noColorArg;
generateArg.setSubArguments({ &inputFileArg, &outputFileArg, &generatorsArg, &clangOptionsArg, &errorResilientArg });
generateArg.setSubArguments({ &inputFileArg, &outputFileArg, &generatorsArg, &clangOptionsArg, &logClangOptions, &errorResilientArg });
JsonSerializationCodeGenerator::Options jsonOptions;
jsonOptions.appendTo(&generateArg);
BinarySerializationCodeGenerator::Options binaryOptions;
@ -50,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;
}
@ -68,20 +69,28 @@ 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);
}
}
}
}
if (logClangOptions.isPresent()) {
cerr << Phrases::Info << "Options passed to clang:" << Phrases::End;
for (const auto &opt : clangOptions) {
cerr << opt << '\n';
}
}
// 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()) {
@ -114,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.";
}

View File

@ -5,6 +5,10 @@
#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>
using namespace std;
@ -64,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
@ -78,31 +82,40 @@ SerializationCodeGenerator::IsRelevant SerializationCodeGenerator::isQualifiedNa
if (isOnlyIncluded(adaptionRecord.record)) {
continue;
}
if (adaptionRecord.qualifiedName == qualifiedName) {
return IsRelevant::Yes;
if (adaptionRecord.qualifiedName == possiblyRelevantClass.qualifiedName) {
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
return;
}
}
// skip all classes which are only included
if (isOnlyIncluded(record)) {
return IsRelevant::No;
if (isOnlyIncluded(possiblyRelevantClass.record)) {
possiblyRelevantClass.isRelevant = IsRelevant::No;
return;
}
// consider all classes inheriting from an instantiation of "JsonSerializable" relevant
if (inheritsFromInstantiationOf(record, m_qualifiedNameOfRecords)) {
return IsRelevant::Yes;
if (const auto *const relevantBase = inheritsFromInstantiationOf(possiblyRelevantClass.record, m_qualifiedNameOfRecords)) {
auto policy = clang::PrintingPolicy(possiblyRelevantClass.record->getASTContext().getLangOpts());
policy.FullyQualifiedName = true;
policy.SuppressScope = false;
policy.SuppressUnwrittenScope = false;
policy.SplitTemplateClosers = false;
possiblyRelevantClass.relevantBase
= clang::TypeName::getFullyQualifiedName(relevantBase->getType(), possiblyRelevantClass.record->getASTContext(), policy, true);
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
return;
}
return IsRelevant::Maybe;
}
std::vector<SerializationCodeGenerator::RelevantClass> SerializationCodeGenerator::findRelevantClasses() const
{
std::vector<RelevantClass> relevantClasses;
for (clang::CXXRecordDecl *record : m_records) {
string qualifiedName(qualifiedNameIfRelevant(record));
if (!qualifiedName.empty()) {
relevantClasses.emplace_back(move(qualifiedName), record);
for (clang::CXXRecordDecl *const record : m_records) {
auto &relevantClass = relevantClasses.emplace_back(record->getQualifiedNameAsString(), record);
computeRelevantClass(relevantClass);
if (relevantClass.isRelevant != IsRelevant::Yes) {
relevantClasses.pop_back();
}
}
return relevantClasses;

View File

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

View File

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

View File

@ -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()
{

View File

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

View File

@ -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).
*/

View File

@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# metadata
set(META_PROJECT_TYPE library)
set(META_HEADER_ONLY_LIB ON)
# add project files
set(HEADER_FILES traits.h)
set(HEADER_FILES traits.h versioning.h)
set(SRC_FILES)
set(TEST_HEADER_FILES)
set(TEST_SRC_FILES)
@ -14,25 +14,26 @@ set(DOC_FILES README.md)
# add JSON-specific sources
if (RapidJSON_FOUND)
list(APPEND HEADER_FILES json/reflector.h json/reflector-boosthana.h json/reflector-chronoutilities.h
json/serializable.h json/errorhandling.h)
list(
APPEND
HEADER_FILES
json/reflector.h
json/reflector-boosthana.h
json/reflector-chronoutilities.h
json/serializable.h
json/errorhandling.h
json/errorformatting.h)
list(APPEND TEST_SRC_FILES tests/jsonreflector.cpp tests/jsonreflector-boosthana.cpp
tests/jsonreflector-chronoutilities.cpp)
tests/jsonreflector-chronoutilities.cpp)
endif ()
# add binary (de)serialization specific sources
list(APPEND HEADER_FILES binary/reflector.h binary/reflector-boosthana.h binary/reflector-chronoutilities.h
binary/serializable.h)
binary/serializable.h)
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)
@ -45,3 +46,8 @@ include(LibraryTarget)
include(TestTarget)
include(Doxygen)
include(ConfigHeader)
# export target name so the generator can link against it
set(${META_PROJECT_VARNAME_UPPER}_TARGET_NAME
"${META_TARGET_NAME}"
PARENT_SCOPE)

View File

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

View File

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

View File

@ -8,6 +8,7 @@
*/
#include "../traits.h"
#include "../versioning.h"
#include <c++utilities/conversion/conversionexception.h>
#include <c++utilities/io/binaryreader.h>
@ -16,9 +17,14 @@
#include <any>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <variant>
/// \cond
class BinaryReflectorTests;
/// \endcond
namespace ReflectiveRapidJSON {
/*!
@ -29,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.
@ -40,23 +47,40 @@ 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;
public:
BinaryDeserializer(std::istream *stream);
explicit BinaryDeserializer(std::istream *stream);
using CppUtilities::BinaryReader::read;
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,
@ -65,24 +89,32 @@ 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;
public:
BinarySerializer(std::ostream *stream);
explicit BinarySerializer(std::ostream *stream);
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;
};
@ -118,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 &) {
@ -131,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();
@ -197,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)
@ -213,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);
}
}
@ -276,9 +324,15 @@ template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinarySeriali
variant);
}
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinarySerializer::write(const Type &customType)
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> *> void BinarySerializer::write(const Type &builtInType, BinaryVersion version)
{
writeCustomType(*this, customType);
CPP_UTILITIES_UNUSED(version)
write(builtInType);
}
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinarySerializer::write(const Type &customType, BinaryVersion version)
{
writeCustomType(*this, customType, version);
}
} // namespace BinaryReflector

View File

@ -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

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# prevent multiple inclusion
if (DEFINED REFLECTION_GENERATOR_MODULE_LOADED)
@ -7,7 +7,7 @@ endif ()
set(REFLECTION_GENERATOR_MODULE_LOADED YES)
# find code generator
set(DEFAULT_REFLECTION_GENERATOR_EXECUTABLE "reflective_rapidjson_generator")
set(DEFAULT_REFLECTION_GENERATOR_EXECUTABLE "${TARGET_PREFIX}reflective_rapidjson_generator${TARGET_SUFFIX}")
set(REFLECTION_GENERATOR_EXECUTABLE
""
CACHE FILEPATH "path to executable of reflection generator")
@ -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(
@ -67,13 +67,36 @@ set(REFLECTION_GENERATOR_TRIPLE
""
CACHE STRING "platform triple for code generator")
function (_reflective_rapidjson_set_prop TARGET_NAME PROPERTY_NAME)
if ("${CMAKE_VERSION}" VERSION_LESS "3.15.0")
set(PROP
"$<TARGET_PROPERTY:${TARGET_NAME},${PROPERTY_NAME}>"
PARENT_SCOPE)
message(
WARNING
"Passing empty flags to the code generator for property ${PROPERTY_NAME} of target ${TARGET_NAME} might not be prevented. Consider updating to CMake 3.15.0 or newer."
)
else ()
set(PROP
"$<FILTER:$<TARGET_PROPERTY:${TARGET_NAME},${PROPERTY_NAME}>,EXCLUDE,^$>"
PARENT_SCOPE)
endif ()
endfunction ()
# define helper function to add a reflection generator invocation for a specified list of source files
include(CMakeParseArguments)
function (add_reflection_generator_invocation)
# parse arguments
set(OPTIONAL_ARGS)
set(ONE_VALUE_ARGS OUTPUT_DIRECTORY JSON_VISIBILITY)
set(MULTI_VALUE_ARGS INPUT_FILES GENERATORS OUTPUT_LISTS CLANG_OPTIONS CLANG_OPTIONS_FROM_TARGETS JSON_CLASSES)
set(OPTIONAL_ARGS ERROR_RESILIENT)
set(ONE_VALUE_ARGS OUTPUT_DIRECTORY JSON_VISIBILITY BINARY_VISBILITY)
set(MULTI_VALUE_ARGS
INPUT_FILES
GENERATORS
OUTPUT_LISTS
CLANG_OPTIONS
CLANG_OPTIONS_FROM_TARGETS
CLANG_OPTIONS_FROM_DEPENDENCIES
JSON_CLASSES)
cmake_parse_arguments(ARGS "${OPTIONAL_ARGS}" "${ONE_VALUE_ARGS}" "${MULTI_VALUE_ARGS}" ${ARGN})
# determine file name or file path if none specified
@ -98,35 +121,46 @@ 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
set(PROP "$<TARGET_PROPERTY:${TARGET_NAME},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
set(PROP "$<TARGET_PROPERTY:${TARGET_NAME},COMPILE_DEFINITIONS>")
_reflective_rapidjson_set_prop("${TARGET_NAME}" COMPILE_DEFINITIONS)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:-D$<JOIN:${PROP},$<SEMICOLON>-D>>")
# add include directories
set(PROP "$<TARGET_PROPERTY:${TARGET_NAME},INCLUDE_DIRECTORIES>")
_reflective_rapidjson_set_prop("${TARGET_NAME}" INCLUDE_DIRECTORIES)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:-I$<JOIN:${PROP},$<SEMICOLON>-I>>")
endforeach ()
endif ()
if (ARGS_CLANG_OPTIONS_FROM_DEPENDENCIES)
foreach (TARGET_NAME ${ARGS_CLANG_OPTIONS_FROM_DEPENDENCIES})
if (NOT TARGET "${TARGET_NAME}")
continue()
endif ()
# add interface compile options
_reflective_rapidjson_set_prop("${TARGET_NAME}" INTERFACE_COMPILE_OPTIONS)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:$<JOIN:${PROP},$<SEMICOLON>>>")
# add interface compile definitions
_reflective_rapidjson_set_prop("${TARGET_NAME}" INTERFACE_COMPILE_DEFINITIONS)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:-D$<JOIN:${PROP},$<SEMICOLON>-D>>")
# add interface include directories
_reflective_rapidjson_set_prop("${TARGET_NAME}" INTERFACE_INCLUDE_DIRECTORIES)
list(APPEND ARGS_CLANG_OPTIONS "$<$<BOOL:${PROP}>:-I$<JOIN:${PROP},$<SEMICOLON>-I>>")
endforeach ()
endif ()
@ -153,6 +187,12 @@ function (add_reflection_generator_invocation)
if (ARGS_JSON_VISIBILITY)
list(APPEND CLI_ARGUMENTS --json-visibility "${ARGS_JSON_VISIBILITY}")
endif ()
if (ARGS_BINARY_VISBILITY)
list(APPEND CLI_ARGUMENTS --binary-visibility "${ARGS_BINARY_VISBILITY}")
endif ()
if (ARGS_ERROR_RESILIENT)
list(APPEND CLI_ARGUMENTS --error-resilient)
endif ()
add_custom_command(
OUTPUT "${OUTPUT_FILE}"
COMMAND "${REFLECTION_GENERATOR_EXECUTABLE}" ARGS ${CLI_ARGUMENTS}

View File

@ -0,0 +1 @@
../../binary

View File

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

View File

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

View File

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

View File

@ -0,0 +1,78 @@
#ifndef REFLECTIVE_RAPIDJSON_JSON_ERROR_FORMATTING_H
#define REFLECTIVE_RAPIDJSON_JSON_ERROR_FORMATTING_H
/*!
* \file errorformatting.h
* \brief Contains helper functions to format errors when deserializing JSON files.
*/
#include "./errorhandling.h"
#include <c++utilities/conversion/stringbuilder.h>
#include <string_view>
namespace ReflectiveRapidJSON {
inline std::string_view jsonTypeToString(JsonType jsonType)
{
switch (jsonType) {
case ReflectiveRapidJSON::JsonType::Null:
return "null";
case ReflectiveRapidJSON::JsonType::Number:
return "number";
case ReflectiveRapidJSON::JsonType::Bool:
return "bool";
case ReflectiveRapidJSON::JsonType::String:
return "string";
case ReflectiveRapidJSON::JsonType::Array:
return "array";
case ReflectiveRapidJSON::JsonType::Object:
return "object";
default:
return "?";
}
}
inline std::string formatJsonDeserializationError(const JsonDeserializationError &error)
{
using namespace CppUtilities;
std::string_view errorKind;
std::string additionalInfo;
switch (error.kind) {
case JsonDeserializationErrorKind::TypeMismatch:
errorKind = "type mismatch";
additionalInfo = ": expected \"" % jsonTypeToString(error.expectedType) % "\", got \"" % jsonTypeToString(error.actualType) + '\"';
break;
case JsonDeserializationErrorKind::ArraySizeMismatch:
errorKind = "array size mismatch";
break;
case JsonDeserializationErrorKind::ConversionError:
errorKind = "conversion error";
break;
case JsonDeserializationErrorKind::UnexpectedDuplicate:
errorKind = "unexpected duplicate";
break;
case JsonDeserializationErrorKind::InvalidVariantObject:
errorKind = "invalid variant object";
break;
case JsonDeserializationErrorKind::InvalidVariantIndex:
errorKind = "invalid variant index";
break;
default:
errorKind = "semantic error";
}
if (error.record && error.member) {
return errorKind % " within record \"" % error.record % "\" and member \"" % error.member % '\"' + additionalInfo;
} else if (error.record && error.index != JsonDeserializationError::noIndex) {
return errorKind % " within record \"" % error.record % "\" and index \"" % error.index % '\"' + additionalInfo;
} else if (error.record) {
return errorKind % " within record \"" % error.record % '\"' + additionalInfo;
} else {
return errorKind % " in document" + additionalInfo;
}
}
} // namespace ReflectiveRapidJSON
#endif // REFLECTIVE_RAPIDJSON_JSON_ERROR_FORMATTING_H

View File

@ -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);
@ -171,7 +172,8 @@ struct JsonDeserializationErrors : public std::vector<JsonDeserializationError>
TypeMismatch = 0x1,
ArraySizeMismatch = 0x2,
ConversionError = 0x4,
UnexpectedDuplicate = 0x8
UnexpectedDuplicate = 0x8,
All = 0xFF,
} throwOn;
private:
@ -219,6 +221,16 @@ template <typename ExpectedType> inline void JsonDeserializationErrors::reportTy
throwMaybe(ThrowOn::TypeMismatch);
}
/*!
* \brief Reports a type mismatch between \tparam expectedType and \a presentType within the current context.
*/
template <RAPIDJSON_NAMESPACE::Type expectedType> inline void JsonDeserializationErrors::reportTypeMismatch(RAPIDJSON_NAMESPACE::Type presentType)
{
emplace_back(
JsonDeserializationErrorKind::TypeMismatch, jsonType(expectedType), jsonType(presentType), currentRecord, currentMember, currentIndex);
throwMaybe(ThrowOn::TypeMismatch);
}
/*!
* \brief Reports an array size mismatch.
* \todo Allow specifying expected and actual size.

View File

@ -19,11 +19,13 @@
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <variant>
#include "./errorhandling.h"
@ -81,9 +83,8 @@ inline RAPIDJSON_NAMESPACE::Document parseJsonDocFromString(const char *json, st
// define traits to distinguish between "built-in" types like int, std::string, std::vector, ... and custom structs/classes
template <typename Type>
using IsBuiltInType = Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>, std::is_enum<Type>,
Traits::IsSpecializationOf<Type, std::tuple>, Traits::IsSpecializationOf<Type, std::pair>, Traits::IsIteratable<Type>,
Traits::IsSpecializationOf<Type, std::unique_ptr>, Traits::IsSpecializationOf<Type, std::shared_ptr>,
Traits::IsSpecializationOf<Type, std::weak_ptr>, IsVariant<Type>>;
Traits::IsSpecializingAnyOf<Type, std::tuple, std::pair>, Traits::IsIteratable<Type>,
Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::shared_ptr, std::weak_ptr, std::optional>, IsVariant<Type>>;
template <typename Type> using IsCustomType = Traits::Not<IsBuiltInType<Type>>;
// define trait to check for custom structs/classes which are JSON serializable
@ -147,12 +148,24 @@ inline void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAP
/*!
* \brief Pushes the specified integer/float/boolean to the specified value.
*/
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>> * = nullptr>
template <typename Type,
Traits::EnableIfAny<
Traits::All<std::is_integral<Type>, Traits::Not<std::is_same<Type, std::uint8_t>>, Traits::Not<std::is_same<Type, std::int8_t>>>,
std::is_floating_point<Type>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
value.Set(reflectable, allocator);
}
/*!
* \brief Pushes the specified 8-bit integer to the specified value.
*/
template <typename Type, Traits::EnableIfAny<std::is_same<Type, std::uint8_t>, std::is_same<Type, std::int8_t>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
value.Set(static_cast<int>(reflectable), allocator);
}
/*!
* \brief Pushes the specified enumeration item to the specified value.
*/
@ -166,19 +179,27 @@ inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
/*!
* \brief Pushes the specified C-string to the specified value.
*/
template <typename Type, Traits::EnableIf<std::is_same<Type, const char *>> * = nullptr>
template <typename Type, Traits::EnableIfAny<std::is_same<Type, const char *>, std::is_same<Type, const char *const &>> * = nullptr>
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
value.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable), allocator);
if (reflectable) {
value.SetString(reflectable, allocator);
} else {
value.SetNull();
}
}
/*!
* \brief Pushes the specified constant 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, const char *const &>> * = nullptr>
inline void push(const char *const &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
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)
{
value.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable), allocator);
if (reflectable.data()) {
value.SetString(reflectable.data(), rapidJsonSize(reflectable.size()), allocator);
} else {
value.SetNull();
}
}
/*!
@ -187,7 +208,7 @@ inline void push(const char *const &reflectable, RAPIDJSON_NAMESPACE::Value &val
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>> * = nullptr>
inline void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
{
value.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), allocator);
value.SetString(reflectable.data(), rapidJsonSize(reflectable.size()), allocator);
}
/*!
@ -198,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);
}
@ -298,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) {
@ -324,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>) {
@ -471,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.
*/
@ -503,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)
{
@ -516,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.
*/
@ -570,7 +611,8 @@ inline void pull(
* \brief Checks whether the specified value contains a string.
* \remarks Does not actually store the value since the ownership would not be clear (see README.md).
*/
template <typename Type, Traits::EnableIfAny<std::is_same<Type, const char *>, std::is_same<Type, const char *const &>> * = nullptr>
template <typename Type,
Traits::EnableIfAny<std::is_same<Type, const char *>, std::is_same<Type, const char *const &>, std::is_same<Type, std::string_view>> * = nullptr>
inline void pull(Type &, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
{
if (!value.IsString()) {
@ -659,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
@ -687,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);
}
}
@ -736,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);
}
}
}
@ -840,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>
@ -890,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);
}
/*!
@ -932,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;
@ -1008,6 +1058,17 @@ RAPIDJSON_NAMESPACE::Document toJsonDocument(const char *reflectable)
return document;
}
/*!
* \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)
{
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kStringType);
document.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), document.GetAllocator());
return document;
}
/*!
* \brief Serializes the specified \a reflectable which can be mapped to an array.
*/

View File

@ -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.

View File

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

View File

@ -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);
}
}

View File

@ -112,7 +112,7 @@ template <> inline void push<NestingArray>(const NestingArray &reflectable, Valu
template <>
inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors)
{
const char *previousRecord;
const char *previousRecord = nullptr;
if (errors) {
previousRecord = errors->currentRecord;
errors->currentRecord = "TestObject";
@ -141,7 +141,7 @@ inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<ch
template <>
inline void pull<NestingObject>(NestingObject &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors)
{
const char *previousRecord;
const char *previousRecord = nullptr;
if (errors) {
previousRecord = errors->currentRecord;
errors->currentRecord = "NestingObject";
@ -156,7 +156,7 @@ inline void pull<NestingObject>(NestingObject &reflectable, const GenericValue<U
template <>
inline void pull<NestingArray>(NestingArray &reflectable, const GenericValue<UTF8<char>>::ConstObject &value, JsonDeserializationErrors *errors)
{
const char *previousRecord;
const char *previousRecord = nullptr;
if (errors) {
previousRecord = errors->currentRecord;
errors->currentRecord = "NestingArray";
@ -186,11 +186,13 @@ class JsonReflectorTests : public TestFixture {
CPPUNIT_TEST(testSerializeNestedObjects);
CPPUNIT_TEST(testSerializeUniquePtr);
CPPUNIT_TEST(testSerializeSharedPtr);
CPPUNIT_TEST(testSerializeOptional);
CPPUNIT_TEST(testDeserializePrimitives);
CPPUNIT_TEST(testDeserializeSimpleObjects);
CPPUNIT_TEST(testDeserializeNestedObjects);
CPPUNIT_TEST(testDeserializeUniquePtr);
CPPUNIT_TEST(testDeserializeSharedPtr);
CPPUNIT_TEST(testDeserializeOptional);
CPPUNIT_TEST(testHandlingParseError);
CPPUNIT_TEST(testHandlingTypeMismatch);
CPPUNIT_TEST_SUITE_END();
@ -205,11 +207,13 @@ public:
void testSerializeNestedObjects();
void testSerializeUniquePtr();
void testSerializeSharedPtr();
void testSerializeOptional();
void testDeserializePrimitives();
void testDeserializeSimpleObjects();
void testDeserializeNestedObjects();
void testDeserializeUniquePtr();
void testDeserializeSharedPtr();
void testDeserializeOptional();
void testHandlingParseError();
void testHandlingTypeMismatch();
@ -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().
*/

View File

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

View File

@ -16,30 +16,22 @@ namespace Traits = ::CppUtilities::Traits;
// define structs and macros to allow treating custom data types as std::map, std::set, ...
/// \brief \brief The TreatAsMapOrHash class allows treating custom classes as std::map or std::unordered_map.
template <typename T> struct TreatAsMapOrHash : public Traits::Bool<false> {
};
template <typename T> struct TreatAsMapOrHash : public Traits::Bool<false> {};
/// \brief \brief The TreatAsMultiMapOrHash class allows treating custom classes as std::multimap or std::unordered_multimap.
template <typename T> struct TreatAsMultiMapOrHash : public Traits::Bool<false> {
};
template <typename T> struct TreatAsMultiMapOrHash : public Traits::Bool<false> {};
/// \brief \brief The TreatAsSet class allows treating custom classes as std::set or std::unordered_set.
template <typename T> struct TreatAsSet : public Traits::Bool<false> {
};
template <typename T> struct TreatAsSet : public Traits::Bool<false> {};
/// \brief \brief The TreatAsMultiSet class allows treating custom classes as std::multiset or std::unordered_multiset.
template <typename T> struct TreatAsMultiSet : public Traits::Bool<false> {
};
template <typename T> struct TreatAsMultiSet : public Traits::Bool<false> {};
#define REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH(T) \
template <> struct TreatAsMapOrHash<T> : public Traits::Bool<true> { \
}
template <> struct TreatAsMapOrHash<T> : public Traits::Bool<true> {}
#define REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH(T) \
template <> struct TreatAsMultiMapOrHash<T> : public Traits::Bool<true> { \
}
template <> struct TreatAsMultiMapOrHash<T> : public Traits::Bool<true> {}
#define REFLECTIVE_RAPIDJSON_TREAT_AS_SET(T) \
template <> struct TreatAsSet<T> : public Traits::Bool<true> { \
}
template <> struct TreatAsSet<T> : public Traits::Bool<true> {}
#define REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_SET(T) \
template <> struct TreatAsMultiSet<T> : public Traits::Bool<true> { \
}
template <> struct TreatAsMultiSet<T> : public Traits::Bool<true> {}
// define traits to check for arrays, sets and maps
template <typename Type>
@ -54,14 +46,16 @@ template <typename Type>
using IsMultiSet
= Traits::Any<Traits::IsSpecializationOf<Type, std::multiset>, Traits::IsSpecializationOf<Type, std::unordered_multiset>, TreatAsMultiSet<Type>>;
template <typename Type>
using IsArrayOrSet = Traits::Any<Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>,
Traits::Not<IsMapOrHash<Type>>, Traits::Not<IsMultiMapOrHash<Type>>>,
using IsArrayOrSet = Traits::Any<
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>,
Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string_view>>, Traits::Not<IsMapOrHash<Type>>, Traits::Not<IsMultiMapOrHash<Type>>>,
TreatAsSet<Type>, TreatAsMultiSet<Type>>;
template <typename Type>
using IsArray = Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>,
Traits::Not<IsMapOrHash<Type>>, Traits::Not<IsMultiMapOrHash<Type>>, Traits::Not<IsSet<Type>>, Traits::Not<IsMultiSet<Type>>>;
template <typename Type>
using IsIteratableExceptString = Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>;
using IsIteratableExceptString = Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>,
Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>;
template <typename Type> using IsVariant = Traits::All<Traits::IsSpecializationOf<Type, std::variant>>;
} // namespace ReflectiveRapidJSON

60
lib/versioning.h Normal file
View File

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