Compare commits
94 Commits
Author | SHA1 | Date |
---|---|---|
Martchus | cc1641e0f8 | |
Martchus | 1ea5b1e744 | |
Martchus | 7c8ef68155 | |
Martchus | e3e4596481 | |
Martchus | 091d521152 | |
Martchus | 9a6d550d8f | |
Martchus | e82d834bf2 | |
Martchus | 27b029ba67 | |
Martchus | d50a4c6004 | |
Martchus | 10bb97bbc4 | |
Martchus | b1bd782910 | |
Martchus | 2b0048f144 | |
Martchus | 5c7a6cba8c | |
Martchus | 60d761f7ed | |
Martchus | 9c3bf01c8f | |
Martchus | 1e3417f8d0 | |
Martchus | a6b9d771aa | |
Martchus | 762540f5e5 | |
Martchus | 0633923935 | |
Martchus | 11491b1387 | |
Martchus | df787f3105 | |
Martchus | 2cc044c705 | |
Martchus | 4966625d8b | |
Martchus | efaa8a8441 | |
Martchus | 22611457f9 | |
Martchus | 59ff3c19eb | |
Martchus | f8f551a78a | |
Martchus | 8b66ca3e6b | |
Martchus | 44c6b8c609 | |
Martchus | 13428667f8 | |
Martchus | 852dfb7e3c | |
Martchus | 0a902ac30c | |
Martchus | 5e72012ed5 | |
Martchus | a4dd52acfa | |
Martchus | e3d32ddfa1 | |
Martchus | 8f1909dfdf | |
Martchus | 874c964e0b | |
Martchus | 30735ba187 | |
Martchus | 5110cff5eb | |
Martchus | dc7c74c497 | |
Martchus | 80183f5269 | |
Martchus | 5c49a438ad | |
Martchus | 6252a7335a | |
Martchus | 2b6634d574 | |
Martchus | 0010e32515 | |
Martchus | 1970145b90 | |
Martchus | 1e95c3d1ca | |
Martchus | c33e1687cb | |
Martchus | 4ff49156da | |
Martchus | 18716b17be | |
otreblan | beb4491b18 | |
Martchus | 8c032ee7a6 | |
Martchus | c4f622df8d | |
Martchus | 442359f5bf | |
Martchus | 86481fa459 | |
Martchus | cf41f4596a | |
Martchus | 4b4d674f56 | |
Martchus | 891b96a38f | |
Martchus | c68d9ea384 | |
Martchus | e7bbdd0af6 | |
Martchus | ed3f89953f | |
Martchus | 6e4077eed2 | |
Martchus | 2f1098ae3d | |
Martchus | b37f467dcd | |
Martchus | 7482c64931 | |
Martchus | f9fc9e02b7 | |
Martchus | 72a11c22b1 | |
Martchus | 48639b42b2 | |
Martchus | 717fb2f037 | |
Martchus | 450588af89 | |
Martchus | 022a174028 | |
Martchus | 1192c2d74a | |
Martchus | 03563aafbf | |
Martchus | f75c8d77a2 | |
Martchus | 456702e009 | |
Martchus | 3374e4ea6c | |
Martchus | e29db0fa87 | |
Martchus | 1024b8e391 | |
Martchus | c3dc381425 | |
Martchus | dd652b4de7 | |
Martchus | 7e5a32265d | |
Martchus | 00dd569869 | |
Martchus | d8e626d259 | |
Martchus | d7e7bdb703 | |
Martchus | fecde7d2d1 | |
Martchus | f21f255e94 | |
Martchus | 8394c145f6 | |
Martchus | 5f7c18b59e | |
Martchus | 8f06bf0272 | |
Martchus | a2b40753c3 | |
Martchus | 6542eab80d | |
Martchus | 8cac133104 | |
Martchus | 2194000938 | |
Martchus | dd174920fe |
|
@ -0,0 +1,19 @@
|
|||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- feature request
|
||||
- enhancement
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
|
@ -1,23 +1,21 @@
|
|||
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
# metadata
|
||||
# set metadata
|
||||
project(reflective_rapidjson)
|
||||
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}/${META_PROJECT_NAME}")
|
||||
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/reflective-rapidjson")
|
||||
set(META_APP_DESCRIPTION "Reflection for serializing/deserializing with RapidJSON")
|
||||
set(META_APP_CATEGORIES "Utility;")
|
||||
set(META_GUI_OPTIONAL false)
|
||||
set(META_VERSION_MAJOR 0)
|
||||
set(META_VERSION_MINOR 0)
|
||||
set(META_VERSION_PATCH 8)
|
||||
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()
|
||||
|
||||
|
@ -47,16 +45,13 @@ else()
|
|||
endif()
|
||||
|
||||
# find c++utilities
|
||||
find_package(c++utilities 4.17.0 REQUIRED)
|
||||
|
||||
# use the source directory of c++utilities for includes rather than the location where headers are going to be installed
|
||||
# note: this enables the tests to find the header files for c++utilities in case it is built within the same project
|
||||
if(CPP_UTILITIES_SOURCE_DIR)
|
||||
set(CPP_UTILITIES_INCLUDE_DIRS "${CPP_UTILITIES_SOURCE_DIR}/..")
|
||||
endif()
|
||||
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.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()
|
||||
|
|
162
README.md
162
README.md
|
@ -49,33 +49,36 @@ For a full list of further ideas, see [TODOs.md](./TODOs.md).
|
|||
## Supported datatypes
|
||||
The following table shows the mapping of supported C++ types to supported JSON types:
|
||||
|
||||
| C++ type | JSON type |
|
||||
| ------------------------------------------------------------- |:------------:|
|
||||
| custom structures/classes | object |
|
||||
| `bool` | true/false |
|
||||
| signed and unsigned integral types | number |
|
||||
| `float` and `double` | number |
|
||||
| `enum` and `enum class` | number |
|
||||
| `std::string` | string |
|
||||
| `const char *` | string |
|
||||
| iteratable lists (`std::vector`, `std::list`, ...) | array |
|
||||
| sets (`std::set`, `std::unordered_set`, `std::multiset`, ...) | array |
|
||||
| `std::tuple` | array |
|
||||
| `std::unique_ptr`, `std::shared_ptr` | depends/null |
|
||||
| `std::map`, `std::unordered_map` | object |
|
||||
| `JsonSerializable` | object |
|
||||
| C++ type | JSON type |
|
||||
| ---------------------------------------------------------------------------- |:------------:|
|
||||
| custom structures/classes | object |
|
||||
| `bool` | true/false |
|
||||
| signed and unsigned integral types | number |
|
||||
| `float` and `double` | number |
|
||||
| `enum` and `enum class` | number |
|
||||
| `std::string` | 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`, `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`.
|
||||
* If multiple `std::shared_ptr` instance might point to the same object this object is serialized multiple times.
|
||||
* 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* true when using binary
|
||||
serialization.
|
||||
`std::shared_ptr` will point to its own copy. Note that this limitation is *not* present when using binary
|
||||
(de)serialization instead of JSON.
|
||||
* For deserialization
|
||||
* iteratables must provide an `emplace_back` method. So deserialization of eg. `std::forward_list`
|
||||
is currently not supported.
|
||||
|
@ -84,11 +87,19 @@ The following table shows the mapping of supported C++ types to supported JSON t
|
|||
* It is possible to treat custom types as set/map using the macro `REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH`,
|
||||
`REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH`, `REFLECTIVE_RAPIDJSON_TREAT_AS_SET` or
|
||||
`REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_SET`.
|
||||
* The key type of the `std::map`, `std::unordered_map` must be `std::string`.
|
||||
* The key type of `std::map`, `std::unordered_map`, `std::multimap` and `std::unordered_multimap` must be
|
||||
`std::string`.
|
||||
* An array is used to represent the multiple values of an `std::multimap` and `std::unordered_multimap` (for
|
||||
consistency also when there is only one value present). This is because the JSON RFC says that
|
||||
"The names within an object SHOULD be unique".
|
||||
* An `std::variant` is represented by an object like `{"index": ..., "data": ...}` where `index` is the
|
||||
zero-based index of the alternative held by the variant and `data` the value held by the variant. The
|
||||
type of `data` is `null` for `std::monostate` and otherwise deduced as usual.
|
||||
* For custom (de)serialization, see the section below.
|
||||
* 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>
|
||||
|
@ -134,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):
|
||||
|
||||
|
@ -146,7 +173,7 @@ struct TestObject : public ReflectiveRapidJSON::BinarySerializable<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)
|
||||
|
||||
|
@ -158,7 +185,7 @@ target_link_libraries(mytarget PRIVATE reflective_rapidjson)
|
|||
# invoke macro
|
||||
add_reflection_generator_invocation(
|
||||
INPUT_FILES code-defining-structs.cpp
|
||||
GENERATORS json
|
||||
GENERATORS json binary
|
||||
OUTPUT_LISTS LIST_OF_GENERATED_HEADERS
|
||||
CLANG_OPTIONS_FROM_TARGETS mytarget
|
||||
)
|
||||
|
@ -174,6 +201,9 @@ will always have the extension `.h`, independently of the extension of the input
|
|||
The full paths of the generated files are also appended to the variable `LIST_OF_GENERATED_HEADERS` which then can be added
|
||||
to the sources of your target. Of course this can be skipped if not required/wanted.
|
||||
|
||||
The `GENERATORS` argument specifies the generators to run. Use `json` to generate code for JSON (de)serialization and `binary`
|
||||
to generate code for binary (de)serialization. As shown in the example, multiple generators can be specified at a time.
|
||||
|
||||
The macro will also automatically pass Clang's resource directory which is detected by invoking `clang -print-resource-dir`.
|
||||
To adjust that, just set the cache variable `REFLECTION_GENERATOR_CLANG_RESOURCE_DIR` before including the module.
|
||||
|
||||
|
@ -198,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.
|
||||
|
@ -224,7 +253,7 @@ Macro's `CLANG_OPTIONS_FROM_TARGETS` argument.
|
|||
The same example as above. However, this time Boost.Hana is used - so it doesn't require invoking the generator.
|
||||
|
||||
<pre>
|
||||
#include "<reflective_rapidjson/json/serializable-boosthana.h>
|
||||
#include <reflective_rapidjson/json/serializable-boosthana.h>
|
||||
|
||||
// define structures using BOOST_HANA_DEFINE_STRUCT, eg.
|
||||
struct TestObject : public JsonSerializable<TestObject> {
|
||||
|
@ -325,6 +354,54 @@ An example for such custom (de)serialization can be found in the file
|
|||
`json/reflector-chronoutilities.h`. It provides (de)serialization of `DateTime` and
|
||||
`TimeSpan` objects from the C++ utilities library mentioned under dependencies.
|
||||
|
||||
### Versioning
|
||||
#### JSON (de)serializer
|
||||
The JSON (de)serializer doesn't support versioning at this point. It'll simply read/write the
|
||||
members present in the struct. Additional members (which were e.g. present in older/newer
|
||||
versions of the struct) are ignored when reading and in consequence dropped when writing.
|
||||
|
||||
#### Binary (de)serializer
|
||||
The binary (de)serializer supports *very* experimental versioning. Otherwise adding/removing
|
||||
members is a breaking change. The versioning looks like this:
|
||||
|
||||
<pre>
|
||||
// enable definition of the macros shown below (otherwise use long macros defined in
|
||||
// `lib/versioning.h`)
|
||||
#define REFLECTIVE_RAPIDJSON_SHORT_MACROS
|
||||
|
||||
#include <reflective_rapidjson/binary/serializable.h>
|
||||
|
||||
// example struct where version is *not* serialized/deserialized; defaults to version from
|
||||
// outer scope when reading/writing, defaults to version 0 on top-level
|
||||
struct Nested : public BinarySerializable<Nested> { //
|
||||
std::uint32_t foo; // will be read/written in any case
|
||||
|
||||
as_of_version(3):
|
||||
std::uint32_t bar; // will be read/written if outer scope version is >= 3
|
||||
};
|
||||
|
||||
// example struct where version is serialized/deserialized; defaults to version 3 when writing
|
||||
struct Example : public BinarySerializable<Example, 3> {
|
||||
Nested nested; // will be read/written in any case, version is "propagated down"
|
||||
std::uint32_t a, b; // will be read/written in any case
|
||||
|
||||
until_version(2):
|
||||
std::uint32_t c, d; // will be read/written if version is <= 2
|
||||
|
||||
as_of_version(3):
|
||||
std::uint32_t e, f; // will be read/written if version is >= 3
|
||||
|
||||
as_of_version(4):
|
||||
std::uint32_t g; // will be read/written if version is >= 4
|
||||
};
|
||||
</pre>
|
||||
|
||||
The version specified as template argument is also assumed to be the highest supported version.
|
||||
If a higher version is encountered during deserialization, `BinaryVersionNotSupported` is thrown
|
||||
and the deserialization aborted.
|
||||
|
||||
Note that the versioning is mostly untested at this point.
|
||||
|
||||
### Remarks
|
||||
* Static member variables and member functions are currently ignored by the generator.
|
||||
* It is currently not possible to ignore a specific member variable.
|
||||
|
@ -354,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)
|
||||
|
@ -373,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 or Clang 5.0/6.0/7.0 as compiler
|
||||
* libstdc++ from GCC 7.2.1/7.3.0/8.1.0
|
||||
* CMake 3.10.1
|
||||
* Clang 5.0.0/5.0.1 for LibTooling
|
||||
* RapidJSON 1.1.0
|
||||
* C++ utilities 4.12
|
||||
* Boost.Hana 1.65.1, 1.66.0, 1.67.0, 1.68.0
|
||||
* CppUnit 1.14.0
|
||||
* Doxygen 1.8.13
|
||||
* 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.
|
||||
|
||||
|
@ -443,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"
|
||||
|
@ -454,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).
|
||||
|
|
5
TODOs.md
5
TODOs.md
|
@ -7,14 +7,14 @@
|
|||
- [x] Add documentation (install instructions, usage)
|
||||
- [x] Allow making 3rdparty classes/structs reflectable
|
||||
- [x] Add additional parameter for code generator to allow specifying relevant classes
|
||||
explicitely
|
||||
explicitly
|
||||
- [x] Fix traits currently relying on `JsonSerializable` being base class
|
||||
- [x] Allow exporting symbols
|
||||
- [x] Fix the massive number of warnings which are currently being created by the code generator (missing `-resource-dir` was the problem)
|
||||
- [ ] Test with libc++ (currently only tested with libstdc++)
|
||||
- [ ] Support templated classes
|
||||
- [ ] Allow (de)serialization of static members (if that makes sense?)
|
||||
- [ ] Allow ignoring particular members or selecting specificly which member variables should be considered
|
||||
- [ ] Allow ignoring particular members or selecting specifically which member variables should be considered
|
||||
* This could work similar to Qt's Signals & Slots macros.
|
||||
* but there should also be a way to do this for 3rdparty types.
|
||||
* Note that currently, *all* public member variables are (de)serialized.
|
||||
|
@ -29,4 +29,5 @@
|
|||
- [x] Support `std::unique_ptr` and `std::shared_ptr`
|
||||
- [x] Support `std::map` and `std::unordered_map`
|
||||
- [ ] Support `std::any`
|
||||
- [x] Support `std::optional`
|
||||
- [x] Support/document customized (de)serialization (eg. serialize some `DateTime` object to ISO string representation)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
# metadata
|
||||
set(META_PROJECT_NAME reflective_rapidjson_generator)
|
||||
|
@ -39,20 +39,29 @@ endif ()
|
|||
# link against c++utilities
|
||||
use_cpp_utilities()
|
||||
|
||||
# find Clang for LibTooling; adding clangTooling should be sufficient as it pulls all transitive dependencies
|
||||
# find Clang for LibTooling
|
||||
find_package(Clang REQUIRED)
|
||||
list(APPEND PRIVATE_LIBRARIES
|
||||
clangTooling
|
||||
clangFrontend
|
||||
clangSerialization
|
||||
clangSema
|
||||
clangAST
|
||||
clangLex
|
||||
clangBasic
|
||||
LLVM)
|
||||
if (TARGET clang-cpp)
|
||||
list(APPEND PRIVATE_LIBRARIES clang-cpp LLVM)
|
||||
else ()
|
||||
list(
|
||||
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)
|
||||
|
@ -63,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(
|
||||
|
@ -79,22 +88,28 @@ 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
|
||||
OtherNotJsonSerializable # test specifying classes for JSON (de)serialization manually
|
||||
SomeOtherClassName # specifying a class that does not exist should not cause any problems
|
||||
JSON_VISIBILITY
|
||||
LIB_EXPORT # not required, just to test setting visibility
|
||||
CPP_UTILITIES_GENERIC_LIB_EXPORT # not required, just to test setting visibility
|
||||
)
|
||||
# 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
|
||||
set(META_CUSTOM_CONFIG "#define CPP_UTILITIES_INCLUDE_DIRS \"${CPP_UTILITIES_INCLUDE_DIRS}\"\n")
|
||||
if (RapidJSON_FOUND)
|
||||
string(APPEND META_CUSTOM_CONFIG "#define RAPIDJSON_INCLUDE_DIRS \"${RAPIDJSON_INCLUDE_DIRS}\"\n")
|
||||
# add include dirs either from RapidJSON_INCLUDE_DIRS or RAPIDJSON_INCLUDE_DIRS
|
||||
if (RapidJSON_INCLUDE_DIRS)
|
||||
set(RAPIDJSON_INCLUDE_DIRS ${RapidJSON_INCLUDE_DIRS})
|
||||
endif ()
|
||||
if (RAPIDJSON_INCLUDE_DIRS)
|
||||
string(APPEND META_CUSTOM_CONFIG "#define RAPIDJSON_INCLUDE_DIRS \"${RAPIDJSON_INCLUDE_DIRS}\"\n")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# add path of Clang's resource dir to config header so test cases can use it
|
||||
|
|
|
@ -5,11 +5,15 @@
|
|||
#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>
|
||||
|
||||
using namespace std;
|
||||
using namespace ApplicationUtilities;
|
||||
using namespace CppUtilities;
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
|
@ -23,7 +27,7 @@ BinarySerializationCodeGenerator::Options::Options()
|
|||
{
|
||||
additionalClassesArg.setRequiredValueCount(Argument::varValueCount);
|
||||
additionalClassesArg.setValueCompletionBehavior(ValueCompletionBehavior::None);
|
||||
visibilityArg.setPreDefinedCompletionValues("LIB_EXPORT");
|
||||
visibilityArg.setPreDefinedCompletionValues("CPP_UTILITIES_GENERIC_LIB_EXPORT");
|
||||
}
|
||||
|
||||
BinarySerializationCodeGenerator::BinarySerializationCodeGenerator(CodeFactory &factory, const Options &options)
|
||||
|
@ -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";
|
||||
}
|
||||
|
||||
|
|
|
@ -16,10 +16,10 @@ public:
|
|||
struct Options {
|
||||
Options();
|
||||
Options(const Options &other) = delete;
|
||||
void appendTo(ApplicationUtilities::Argument *arg);
|
||||
void appendTo(CppUtilities::Argument *arg);
|
||||
|
||||
ApplicationUtilities::ConfigValueArgument additionalClassesArg;
|
||||
ApplicationUtilities::ConfigValueArgument visibilityArg;
|
||||
CppUtilities::ConfigValueArgument additionalClassesArg;
|
||||
CppUtilities::ConfigValueArgument visibilityArg;
|
||||
};
|
||||
|
||||
BinarySerializationCodeGenerator(CodeFactory &factory, const Options &options);
|
||||
|
@ -27,12 +27,12 @@ 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;
|
||||
};
|
||||
|
||||
inline void BinarySerializationCodeGenerator::Options::appendTo(ApplicationUtilities::Argument *arg)
|
||||
inline void BinarySerializationCodeGenerator::Options::appendTo(CppUtilities::Argument *arg)
|
||||
{
|
||||
arg->addSubArgument(&additionalClassesArg);
|
||||
arg->addSubArgument(&visibilityArg);
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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)...));
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ CodeGenerator::~CodeGenerator()
|
|||
*/
|
||||
void CodeGenerator::addDeclaration(clang::Decl *decl)
|
||||
{
|
||||
VAR_UNUSED(decl)
|
||||
CPP_UTILITIES_UNUSED(decl)
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -44,17 +44,18 @@ bool CodeGenerator::isOnlyIncluded(const clang::Decl *declaration) const
|
|||
|
||||
/*!
|
||||
* \brief Returns whether the specified \a record inherits from an instantiation of the specified \a templateClass.
|
||||
* \returns Returns the relevant base class if that's the case and otherwise nullptr.
|
||||
* \remarks The specified \a record must be defined (not only forward-declared).
|
||||
*/
|
||||
bool CodeGenerator::inheritsFromInstantiationOf(clang::CXXRecordDecl *const record, const char *const templateClass)
|
||||
clang::CXXBaseSpecifier *CodeGenerator::inheritsFromInstantiationOf(clang::CXXRecordDecl *const record, const char *const templateClass)
|
||||
{
|
||||
for (const clang::CXXBaseSpecifier &base : record->bases()) {
|
||||
const clang::CXXRecordDecl *const baseDecl = base.getType()->getAsCXXRecordDecl();
|
||||
for (clang::CXXBaseSpecifier &base : record->bases()) {
|
||||
clang::CXXRecordDecl *const baseDecl = base.getType()->getAsCXXRecordDecl();
|
||||
if (baseDecl && baseDecl->getQualifiedNameAsString() == templateClass) {
|
||||
return true;
|
||||
return &base;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace clang {
|
||||
class Decl;
|
||||
class CXXRecordDecl;
|
||||
class CXXBaseSpecifier;
|
||||
class SourceManager;
|
||||
} // namespace clang
|
||||
|
||||
|
@ -32,7 +33,7 @@ protected:
|
|||
CodeFactory &factory() const;
|
||||
void lazyInitializeSourceManager() const;
|
||||
bool isOnlyIncluded(const clang::Decl *declaration) const;
|
||||
static bool inheritsFromInstantiationOf(clang::CXXRecordDecl *record, const char *templateClass);
|
||||
static clang::CXXBaseSpecifier *inheritsFromInstantiationOf(clang::CXXRecordDecl *record, const char *templateClass);
|
||||
|
||||
private:
|
||||
CodeFactory &m_factory;
|
||||
|
|
|
@ -16,7 +16,7 @@ bool FrontendAction::hasCodeCompletionSupport() const
|
|||
REFLECTIVE_RAPIDJSON_MAYBE_UNIQUE(clang::ASTConsumer)
|
||||
FrontendAction::CreateASTConsumer(clang::CompilerInstance &compilerInstance, llvm::StringRef inputFile)
|
||||
{
|
||||
VAR_UNUSED(inputFile)
|
||||
CPP_UTILITIES_UNUSED(inputFile)
|
||||
|
||||
// propagate compiler instance to factory
|
||||
m_factory.setCompilerInstance(&compilerInstance);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace ApplicationUtilities;
|
||||
using namespace CppUtilities;
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
|
@ -23,7 +23,7 @@ JsonSerializationCodeGenerator::Options::Options()
|
|||
{
|
||||
additionalClassesArg.setRequiredValueCount(Argument::varValueCount);
|
||||
additionalClassesArg.setValueCompletionBehavior(ValueCompletionBehavior::None);
|
||||
visibilityArg.setPreDefinedCompletionValues("LIB_EXPORT");
|
||||
visibilityArg.setPreDefinedCompletionValues("CPP_UTILITIES_GENERIC_LIB_EXPORT");
|
||||
}
|
||||
|
||||
JsonSerializationCodeGenerator::JsonSerializationCodeGenerator(CodeFactory &factory, const Options &options)
|
||||
|
@ -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"
|
||||
|
|
|
@ -16,10 +16,10 @@ public:
|
|||
struct Options {
|
||||
Options();
|
||||
Options(const Options &other) = delete;
|
||||
void appendTo(ApplicationUtilities::Argument *arg);
|
||||
void appendTo(CppUtilities::Argument *arg);
|
||||
|
||||
ApplicationUtilities::ConfigValueArgument additionalClassesArg;
|
||||
ApplicationUtilities::ConfigValueArgument visibilityArg;
|
||||
CppUtilities::ConfigValueArgument additionalClassesArg;
|
||||
CppUtilities::ConfigValueArgument visibilityArg;
|
||||
};
|
||||
|
||||
JsonSerializationCodeGenerator(CodeFactory &factory, const Options &options);
|
||||
|
@ -27,12 +27,12 @@ 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;
|
||||
};
|
||||
|
||||
inline void JsonSerializationCodeGenerator::Options::appendTo(ApplicationUtilities::Argument *arg)
|
||||
inline void JsonSerializationCodeGenerator::Options::appendTo(CppUtilities::Argument *arg)
|
||||
{
|
||||
arg->addSubArgument(&additionalClassesArg);
|
||||
arg->addSubArgument(&visibilityArg);
|
||||
|
|
|
@ -6,10 +6,8 @@
|
|||
|
||||
#include <c++utilities/application/argumentparser.h>
|
||||
#include <c++utilities/application/commandlineutils.h>
|
||||
#include <c++utilities/application/failure.h>
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
#include <c++utilities/io/ansiescapecodes.h>
|
||||
#include <c++utilities/io/catchiofailure.h>
|
||||
#include <c++utilities/io/misc.h>
|
||||
|
||||
#include <cstring>
|
||||
|
@ -18,10 +16,8 @@
|
|||
#include <unordered_map>
|
||||
|
||||
using namespace std;
|
||||
using namespace ApplicationUtilities;
|
||||
using namespace ConversionUtilities;
|
||||
using namespace EscapeCodes;
|
||||
using namespace IoUtilities;
|
||||
using namespace CppUtilities;
|
||||
using namespace CppUtilities::EscapeCodes;
|
||||
using namespace ReflectiveRapidJSON;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
|
@ -37,16 +33,17 @@ int main(int argc, char *argv[])
|
|||
inputFileArg.setRequired(true);
|
||||
ConfigValueArgument outputFileArg("output-file", '\0', "specifies the output file", { "path" });
|
||||
Argument generatorsArg("generators", '\0', "specifies the generators (by default all generators are enabled)");
|
||||
generatorsArg.setValueNames({ "json" });
|
||||
generatorsArg.setPreDefinedCompletionValues("json");
|
||||
generatorsArg.setValueNames({ "json", "binary" });
|
||||
generatorsArg.setPreDefinedCompletionValues("json binary");
|
||||
generatorsArg.setRequiredValueCount(Argument::varValueCount);
|
||||
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;
|
||||
|
@ -54,7 +51,7 @@ int main(int argc, char *argv[])
|
|||
parser.setMainArguments({ &generateArg, &noColorArg, &helpArg });
|
||||
|
||||
// parse arguments
|
||||
parser.parseArgsOrExit(argc, argv);
|
||||
parser.parseArgs(argc, argv);
|
||||
if (helpArg.isPresent() || !generateArg.isPresent()) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -72,21 +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));
|
||||
clangOptions.reserve(clangOptions.size() + splittedValues.size());
|
||||
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()) {
|
||||
|
@ -119,15 +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 (...) {
|
||||
catchIoFailure();
|
||||
const char *errorMessage;
|
||||
} catch (const std::ios_base::failure &failure) {
|
||||
const char *errorMessage = failure.what();
|
||||
if (os) {
|
||||
errorMessage = os->fail() || os->bad() ? "An IO error occured when writing to the output stream." : "An IO error occured.";
|
||||
errorMessage = os->fail() || os->bad() ? "An IO error occurred when writing to the output stream." : "An IO error occurred.";
|
||||
} else {
|
||||
errorMessage = "An IO error when opening output stream.";
|
||||
}
|
||||
|
|
|
@ -5,6 +5,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;
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#include <llvm/ADT/StringRef.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, llvm::StringRef str);
|
||||
|
@ -15,11 +17,14 @@ std::ostream &operator<<(std::ostream &os, llvm::StringRef str);
|
|||
*/
|
||||
class SerializationCodeGenerator : public CodeGenerator {
|
||||
public:
|
||||
enum class IsRelevant { Yes, No, Maybe };
|
||||
struct RelevantClass {
|
||||
explicit RelevantClass(std::string &&qualifiedName, clang::CXXRecordDecl *record);
|
||||
|
||||
std::string qualifiedName;
|
||||
clang::CXXRecordDecl *record;
|
||||
std::string relevantBase;
|
||||
clang::CXXRecordDecl *record = nullptr;
|
||||
IsRelevant isRelevant = IsRelevant::Maybe;
|
||||
};
|
||||
|
||||
SerializationCodeGenerator(CodeFactory &factory);
|
||||
|
@ -27,9 +32,7 @@ public:
|
|||
void addDeclaration(clang::Decl *decl) override;
|
||||
|
||||
protected:
|
||||
enum class IsRelevant { Yes, No, Maybe };
|
||||
IsRelevant isQualifiedNameIfRelevant(clang::CXXRecordDecl *record, const std::string &qualifiedName) const;
|
||||
virtual std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const = 0;
|
||||
virtual void computeRelevantClass(RelevantClass &possiblyRelevantClass) const;
|
||||
std::vector<RelevantClass> findRelevantClasses() const;
|
||||
static std::vector<const RelevantClass *> findRelevantBaseClasses(
|
||||
const RelevantClass &relevantClass, const std::vector<RelevantClass> &relevantBases);
|
||||
|
|
|
@ -13,7 +13,7 @@ template <> void pull<::TestNamespace1::Person>(::TestNamespace1::Person &refle
|
|||
{
|
||||
// pull base classes
|
||||
// set error context for current record
|
||||
const char *previousRecord;
|
||||
const char *previousRecord = nullptr;
|
||||
if (errors) {
|
||||
previousRecord = errors->currentRecord;
|
||||
errors->currentRecord = "TestNamespace1::Person";
|
||||
|
@ -39,7 +39,7 @@ template <> void pull<::TestNamespace2::ThirdPartyStruct>(::TestNamespace2::Thi
|
|||
{
|
||||
// pull base classes
|
||||
// set error context for current record
|
||||
const char *previousRecord;
|
||||
const char *previousRecord = nullptr;
|
||||
if (errors) {
|
||||
previousRecord = errors->currentRecord;
|
||||
errors->currentRecord = "TestNamespace2::ThirdPartyStruct";
|
||||
|
|
|
@ -19,10 +19,8 @@
|
|||
#include <sstream>
|
||||
|
||||
using namespace CPPUNIT_NS;
|
||||
using namespace IoUtilities;
|
||||
using namespace TestUtilities;
|
||||
using namespace TestUtilities::Literals;
|
||||
using namespace ConversionUtilities;
|
||||
using namespace CppUtilities;
|
||||
using namespace CppUtilities::Literals;
|
||||
|
||||
/*!
|
||||
* \brief The BinaryGeneratorTests class tests the binary generator.
|
||||
|
|
|
@ -6,10 +6,12 @@
|
|||
#include <c++utilities/tests/testutils.h>
|
||||
|
||||
// ensure "operator<<" from TestUtilities is visible prior to the call site
|
||||
using TestUtilities::operator<<;
|
||||
using CppUtilities::operator<<;
|
||||
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
namespace Traits = CppUtilities::Traits;
|
||||
|
||||
/*!
|
||||
* \brief Asserts equality of two iteratables printing the differing indices.
|
||||
*/
|
||||
|
@ -22,20 +24,19 @@ inline void assertEqualityLinewise(const Iteratable &iteratable1, const Iteratab
|
|||
for (auto i1 = iteratable1.cbegin(), i2 = iteratable2.cbegin(); i1 != iteratable1.cend() || i2 != iteratable2.cend(); ++currentLine) {
|
||||
if (i1 != iteratable1.cend() && i2 != iteratable2.cend()) {
|
||||
if (*i1 != *i2) {
|
||||
differentLines.push_back(ConversionUtilities::numberToString(currentLine));
|
||||
differentLines.push_back(CppUtilities::numberToString(currentLine));
|
||||
}
|
||||
++i1, ++i2;
|
||||
} else if (i1 != iteratable1.cend()) {
|
||||
differentLines.push_back(ConversionUtilities::numberToString(currentLine));
|
||||
differentLines.push_back(CppUtilities::numberToString(currentLine));
|
||||
++i1;
|
||||
} else if (i2 != iteratable1.cend()) {
|
||||
differentLines.push_back(ConversionUtilities::numberToString(currentLine));
|
||||
differentLines.push_back(CppUtilities::numberToString(currentLine));
|
||||
++i2;
|
||||
}
|
||||
}
|
||||
if (!differentLines.empty()) {
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(
|
||||
"the following lines differ: " + ConversionUtilities::joinStrings(differentLines, ", "), iteratable1, iteratable2);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("the following lines differ: " + CppUtilities::joinStrings(differentLines, ", "), iteratable1, iteratable2);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,10 +17,8 @@
|
|||
#include <iostream>
|
||||
|
||||
using namespace CPPUNIT_NS;
|
||||
using namespace IoUtilities;
|
||||
using namespace TestUtilities;
|
||||
using namespace TestUtilities::Literals;
|
||||
using namespace ConversionUtilities;
|
||||
using namespace CppUtilities;
|
||||
using namespace CppUtilities::Literals;
|
||||
|
||||
/*!
|
||||
* \brief The JsonGeneratorTests class tests the overall functionality of the code generator (CLI and generator itself) and JSON specific parts.
|
||||
|
@ -66,10 +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, "-I", CPP_UTILITIES_INCLUDE_DIRS, "-I",
|
||||
RAPIDJSON_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
|
||||
#endif
|
||||
};
|
||||
|
||||
stringstream buffer;
|
||||
JsonSerializationCodeGenerator::Options jsonOptions;
|
||||
|
@ -82,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.
|
||||
*/
|
||||
|
@ -92,9 +94,12 @@ void JsonGeneratorTests::testCLI()
|
|||
string stdout, stderr;
|
||||
|
||||
const string inputFilePath(testFilePath("some_structs.h"));
|
||||
const char *const args1[]
|
||||
= { PROJECT_NAME, "--input-file", inputFilePath.data(), "--json-classes", "TestNamespace2::ThirdPartyStruct", "--clang-opt", "-resource-dir",
|
||||
REFLECTION_GENERATOR_CLANG_RESOURCE_DIR, "-I", CPP_UTILITIES_INCLUDE_DIRS, "-I", RAPIDJSON_INCLUDE_DIRS, nullptr };
|
||||
const char *const args1[] = { PROJECT_NAME, "--input-file", inputFilePath.data(), "--json-classes", "TestNamespace2::ThirdPartyStruct",
|
||||
"--clang-opt", "-resource-dir", REFLECTION_GENERATOR_CLANG_RESOURCE_DIR, "-std=c++17", "-I", CPP_UTILITIES_INCLUDE_DIRS,
|
||||
#ifdef RAPIDJSON_INCLUDE_DIRS
|
||||
"-I", RAPIDJSON_INCLUDE_DIRS,
|
||||
#endif
|
||||
nullptr };
|
||||
TESTUTILS_ASSERT_EXEC(args1);
|
||||
assertEqualityLinewise(m_expectedCode, toArrayOfLines(stdout));
|
||||
#endif
|
||||
|
@ -159,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()
|
||||
{
|
||||
|
@ -188,7 +193,7 @@ void JsonGeneratorTests::testSingleInheritence()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Like testIncludingGeneratedHeader() but also tests multiple inheritence.
|
||||
* \brief Like testIncludingGeneratedHeader() but also tests multiple inheritance.
|
||||
*/
|
||||
void JsonGeneratorTests::testMultipleInheritence()
|
||||
{
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
#ifndef REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H
|
||||
#define REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H
|
||||
|
||||
#define REFLECTIVE_RAPIDJSON_SHORT_MACROS
|
||||
|
||||
#include "../../lib/binary/serializable.h"
|
||||
#include "../../lib/json/serializable.h"
|
||||
#include "../../lib/versioning.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ReflectiveRapidJSON;
|
||||
|
@ -67,4 +70,29 @@ struct PointerStruct : public BinarySerializable<PointerStruct> {
|
|||
std::shared_ptr<PointerTarget> s3;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief All of this is supposed to work if classes are within a namespace so let's use a namespace here.
|
||||
*/
|
||||
namespace SomeNamespace {
|
||||
|
||||
/*!
|
||||
* \brief The PointerStruct struct is used to test the behavior of the binary (de)serialization with smart pointer.
|
||||
*/
|
||||
// clang-format off
|
||||
struct VersionedStruct : public BinarySerializable<VersionedStruct, 3> {
|
||||
std::uint32_t a, b;
|
||||
|
||||
until_version(2):
|
||||
std::uint32_t c, d;
|
||||
|
||||
as_of_version(3):
|
||||
std::uint32_t e, f;
|
||||
|
||||
as_of_version(4):
|
||||
std::uint32_t g;
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
} // namespace SomeNamespace
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H
|
||||
|
|
|
@ -99,8 +99,8 @@ struct MultipleDerivedTestStruct : public TestStruct,
|
|||
* and toJson() methods. This is asserted in JsonGeneratorTests::testCustomSerialization();
|
||||
*/
|
||||
struct StructWithCustomTypes : public JsonSerializable<StructWithCustomTypes> {
|
||||
ChronoUtilities::DateTime dt = ChronoUtilities::DateTime::fromDateAndTime(2017, 4, 2, 15, 31, 21, 165.125);
|
||||
ChronoUtilities::TimeSpan ts = ChronoUtilities::TimeSpan::fromHours(3.25) + ChronoUtilities::TimeSpan::fromSeconds(19.125);
|
||||
CppUtilities::DateTime dt = CppUtilities::DateTime::fromDateAndTime(2017, 4, 2, 15, 31, 21, 165.125);
|
||||
CppUtilities::TimeSpan ts = CppUtilities::TimeSpan::fromHours(3.25) + CppUtilities::TimeSpan::fromSeconds(19.125);
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -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).
|
||||
*/
|
||||
|
|
|
@ -34,7 +34,7 @@ bool Visitor::VisitDecl(clang::Decl *decl)
|
|||
*/
|
||||
bool ReflectiveRapidJSON::Visitor::VisitFunctionDecl(clang::FunctionDecl *func)
|
||||
{
|
||||
VAR_UNUSED(func)
|
||||
CPP_UTILITIES_UNUSED(func)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ bool ReflectiveRapidJSON::Visitor::VisitFunctionDecl(clang::FunctionDecl *func)
|
|||
*/
|
||||
bool ReflectiveRapidJSON::Visitor::VisitStmt(clang::Stmt *st)
|
||||
{
|
||||
VAR_UNUSED(st)
|
||||
CPP_UTILITIES_UNUSED(st)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ bool ReflectiveRapidJSON::Visitor::VisitStmt(clang::Stmt *st)
|
|||
*/
|
||||
bool Visitor::VisitNamespaceDecl(clang::NamespaceDecl *decl)
|
||||
{
|
||||
VAR_UNUSED(decl)
|
||||
CPP_UTILITIES_UNUSED(decl)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ bool Visitor::VisitNamespaceDecl(clang::NamespaceDecl *decl)
|
|||
*/
|
||||
bool Visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *decl)
|
||||
{
|
||||
VAR_UNUSED(decl)
|
||||
CPP_UTILITIES_UNUSED(decl)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,43 +14,30 @@ 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 TEST_SRC_FILES
|
||||
tests/jsonreflector.cpp
|
||||
tests/jsonreflector-boosthana.cpp
|
||||
tests/jsonreflector-chronoutilities.cpp)
|
||||
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)
|
||||
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)
|
||||
list(APPEND TEST_SRC_FILES
|
||||
tests/traits.cpp
|
||||
tests/binaryreflector.cpp
|
||||
tests/binaryreflector-boosthana.cpp)
|
||||
list(APPEND HEADER_FILES binary/reflector.h binary/reflector-boosthana.h binary/reflector-chronoutilities.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_SHARED_INCLUDE_DIRS "${CPP_UTILITIES_SOURCE_DIR}/..")
|
||||
list(APPEND PUBLIC_STATIC_INCLUDE_DIRS "${CPP_UTILITIES_SOURCE_DIR}/..")
|
||||
else ()
|
||||
list(APPEND PUBLIC_SHARED_INCLUDE_DIRS "${CPP_UTILITIES_INCLUDE_DIRS}")
|
||||
list(APPEND PUBLIC_STATIC_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)
|
||||
list(APPEND PUBLIC_SHARED_INCLUDE_DIRS ${RAPIDJSON_INCLUDE_DIRS})
|
||||
list(APPEND PUBLIC_STATIC_INCLUDE_DIRS ${RAPIDJSON_INCLUDE_DIRS})
|
||||
list(APPEND PUBLIC_INCLUDE_DIRS ${RAPIDJSON_INCLUDE_DIRS})
|
||||
endif ()
|
||||
|
||||
# include modules to apply configuration
|
||||
|
@ -59,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)
|
||||
|
|
|
@ -24,16 +24,17 @@
|
|||
namespace ReflectiveRapidJSON {
|
||||
namespace BinaryReflector {
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void readCustomType(BinaryDeserializer &deserializer, Type &customType)
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *>
|
||||
BinaryVersion readCustomType(BinaryDeserializer &deserializer, Type &customType, BinaryVersion version)
|
||||
{
|
||||
boost::hana::for_each(
|
||||
boost::hana::keys(customType), [&deserializer, &customType](auto key) { deserializer.read(boost::hana::at_key(customType, key)); });
|
||||
boost::hana::for_each(boost::hana::keys(customType), [&](auto key) { deserializer.read(boost::hana::at_key(customType, key), version); });
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void writeCustomType(BinarySerializer &serializer, const Type &customType)
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *>
|
||||
void writeCustomType(BinarySerializer &serializer, const Type &customType, BinaryVersion version)
|
||||
{
|
||||
boost::hana::for_each(
|
||||
boost::hana::keys(customType), [&serializer, &customType](auto key) { serializer.write(boost::hana::at_key(customType, key)); });
|
||||
boost::hana::for_each(boost::hana::keys(customType), [&](auto key) { serializer.write(boost::hana::at_key(customType, key), version); });
|
||||
}
|
||||
|
||||
} // namespace BinaryReflector
|
||||
|
|
|
@ -16,23 +16,33 @@
|
|||
namespace ReflectiveRapidJSON {
|
||||
namespace BinaryReflector {
|
||||
|
||||
template <> inline void readCustomType<ChronoUtilities::DateTime>(BinaryDeserializer &deserializer, ChronoUtilities::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<ChronoUtilities::DateTime>(BinarySerializer &serializer, const ChronoUtilities::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<ChronoUtilities::TimeSpan>(BinaryDeserializer &deserializer, ChronoUtilities::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<ChronoUtilities::TimeSpan>(BinarySerializer &serializer, const ChronoUtilities::TimeSpan &timeSpan)
|
||||
template <>
|
||||
inline void writeCustomType<CppUtilities::TimeSpan>(BinarySerializer &serializer, const CppUtilities::TimeSpan &timeSpan, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
serializer.write(timeSpan.totalTicks());
|
||||
}
|
||||
|
||||
|
|
|
@ -8,17 +8,22 @@
|
|||
*/
|
||||
|
||||
#include "../traits.h"
|
||||
#include "../versioning.h"
|
||||
|
||||
#include <c++utilities/conversion/conversionexception.h>
|
||||
#include <c++utilities/conversion/types.h>
|
||||
#include <c++utilities/io/binaryreader.h>
|
||||
#include <c++utilities/io/binarywriter.h>
|
||||
|
||||
#include <any>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
|
||||
/// \cond
|
||||
class BinaryReflectorTests;
|
||||
/// \endcond
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
|
@ -30,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.
|
||||
|
@ -39,53 +45,81 @@ namespace BinaryReflector {
|
|||
|
||||
// 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<Traits::IsAnyOf<Type, char, byte, bool, std::string, int16, uint16, int32, uint32, int64, uint64, float32, float64>,
|
||||
Traits::IsIteratable<Type>, Traits::IsSpecializingAnyOf<Type, std::pair, std::unique_ptr, std::shared_ptr>, std::is_enum<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::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;
|
||||
|
||||
class BinaryDeserializer : public IoUtilities::BinaryReader {
|
||||
public:
|
||||
BinaryDeserializer(std::istream *stream);
|
||||
explicit BinaryDeserializer(std::istream *stream);
|
||||
|
||||
using IoUtilities::BinaryReader::read;
|
||||
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 &pair);
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> * = 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,
|
||||
Traits::EnableIf<IsIteratableExceptString<Type>,
|
||||
Traits::None<IsMapOrHash<Type>, IsMultiMapOrHash<Type>, Traits::All<IsArray<Type>, Traits::IsResizable<Type>>>> * = nullptr>
|
||||
void read(Type &iteratable);
|
||||
template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void read(Type &customType);
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void read(Type &customType);
|
||||
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<IsBuiltInType<Type>> * = nullptr> BinaryVersion read(Type &builtInType, BinaryVersion version);
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> BinaryVersion read(Type &customType, BinaryVersion version = 0);
|
||||
|
||||
std::unordered_map<uint64, std::any> m_pointer;
|
||||
private:
|
||||
std::unordered_map<std::uint64_t, std::any> m_pointer;
|
||||
};
|
||||
|
||||
class BinarySerializer : public IoUtilities::BinaryWriter {
|
||||
public:
|
||||
BinarySerializer(std::ostream *stream);
|
||||
/// \brief The BinarySerializer class can write various data types, including custom ones, to an std::ostream.
|
||||
class BinarySerializer : public CppUtilities::BinaryWriter {
|
||||
friend class ::BinaryReflectorTests;
|
||||
|
||||
using IoUtilities::BinaryWriter::write;
|
||||
public:
|
||||
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 &customType);
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void write(const Type &customType);
|
||||
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<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);
|
||||
|
||||
std::unordered_map<uint64, bool> m_pointer;
|
||||
private:
|
||||
std::unordered_map<std::uint64_t, bool> m_pointer;
|
||||
};
|
||||
|
||||
inline BinaryDeserializer::BinaryDeserializer(std::istream *stream)
|
||||
: IoUtilities::BinaryReader(stream)
|
||||
: CppUtilities::BinaryReader(stream)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -116,19 +150,29 @@ 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 &) {
|
||||
throw ConversionUtilities::ConversionException("Referenced pointer type does not match");
|
||||
throw CppUtilities::ConversionException("Referenced pointer type does not match");
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -168,13 +212,46 @@ template <typename Type, Traits::EnableIf<std::is_enum<Type>> *> void BinaryDese
|
|||
enumValue = static_cast<Type>(value);
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinaryDeserializer::read(Type &customType)
|
||||
/// \cond
|
||||
namespace Detail {
|
||||
template <typename Variant, std::size_t compiletimeIndex = 0>
|
||||
void readVariantValueByRuntimeIndex(std::size_t runtimeIndex, Variant &variant, BinaryDeserializer &deserializer)
|
||||
{
|
||||
readCustomType(*this, customType);
|
||||
if constexpr (compiletimeIndex < std::variant_size_v<Variant>) {
|
||||
if (compiletimeIndex == runtimeIndex) {
|
||||
if constexpr (std::is_same_v<std::variant_alternative_t<compiletimeIndex, Variant>, std::monostate>) {
|
||||
variant = std::monostate{};
|
||||
} else {
|
||||
deserializer.read(variant.template emplace<compiletimeIndex>());
|
||||
}
|
||||
} else {
|
||||
readVariantValueByRuntimeIndex<Variant, compiletimeIndex + 1>(runtimeIndex, variant, deserializer);
|
||||
}
|
||||
} else {
|
||||
throw CppUtilities::ConversionException("Variant index is out of expected range");
|
||||
}
|
||||
}
|
||||
} // namespace Detail
|
||||
/// \endcond
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinaryDeserializer::read(Type &variant)
|
||||
{
|
||||
Detail::readVariantValueByRuntimeIndex(readByte(), variant, *this);
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> *> BinaryVersion BinaryDeserializer::read(Type &builtInType, BinaryVersion version)
|
||||
{
|
||||
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)
|
||||
: IoUtilities::BinaryWriter(stream)
|
||||
: CppUtilities::BinaryWriter(stream)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -184,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,7 +279,7 @@ template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std:
|
|||
const auto id = reinterpret_cast<std::uintptr_t>(pointer.get());
|
||||
const auto bigId = id >= 0x80000000000000;
|
||||
auto &alreadyWritten = m_pointer[id];
|
||||
byte mode = alreadyWritten ? 2 : 1;
|
||||
std::uint8_t mode = alreadyWritten ? 2 : 1;
|
||||
if (bigId) {
|
||||
mode = mode | 0x4; // "flag" 3rd bit to indicate big ID
|
||||
}
|
||||
|
@ -232,9 +309,30 @@ template <typename Type, Traits::EnableIf<std::is_enum<Type>> *> void BinarySeri
|
|||
write(static_cast<typename std::underlying_type<Type>::type>(enumValue));
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinarySerializer::write(const Type &customType)
|
||||
template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinarySerializer::write(const Type &variant)
|
||||
{
|
||||
writeCustomType(*this, customType);
|
||||
static_assert(std::variant_size_v<Type> < std::numeric_limits<std::uint8_t>::max(), "index will not exceed limit");
|
||||
writeByte(static_cast<std::uint8_t>(variant.index()));
|
||||
std::visit(
|
||||
[this](const auto &valueOfActualType) {
|
||||
if constexpr (!std::is_same_v<std::decay_t<decltype(valueOfActualType)>, std::monostate>) {
|
||||
write(valueOfActualType);
|
||||
} else {
|
||||
CPP_UTILITIES_UNUSED(this)
|
||||
}
|
||||
},
|
||||
variant);
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> *> void BinarySerializer::write(const Type &builtInType, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
write(builtInType);
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *> void BinarySerializer::write(const Type &customType, BinaryVersion version)
|
||||
{
|
||||
writeCustomType(*this, customType, version);
|
||||
}
|
||||
|
||||
} // namespace BinaryReflector
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
/*!
|
||||
* \file serializable.h
|
||||
* \brief Contains only the definiation of the BinarySerializable template class which makes the reflection
|
||||
* \brief Contains only the definition of the BinarySerializable template class which makes the reflection
|
||||
* accessible. The actual implementation is found in binaryreflector.h and generated files.
|
||||
*/
|
||||
|
||||
|
@ -14,28 +14,36 @@
|
|||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
using BinaryVersionNotSupported = VersionNotSupported<BinaryVersion>;
|
||||
|
||||
/*!
|
||||
* \brief The BinarySerializable class provides the CRTP-base for (de)serializable objects.
|
||||
*/
|
||||
template <typename Type> struct BinarySerializable {
|
||||
void toBinary(std::ostream &outputStream) const;
|
||||
void restoreFromBinary(std::istream &inputStream);
|
||||
template <typename Type, BinaryVersion v> struct BinarySerializable {
|
||||
using VersionNotSupported = BinaryVersionNotSupported;
|
||||
void toBinary(std::ostream &outputStream, BinaryVersion version = 0) const;
|
||||
BinaryVersion restoreFromBinary(std::istream &inputStream);
|
||||
static Type fromBinary(std::istream &inputStream);
|
||||
|
||||
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::BinarySerializable";
|
||||
static constexpr auto version = v;
|
||||
|
||||
#if __cplusplus > 201707L
|
||||
bool operator==(const BinarySerializable<Type, v> &) const = default;
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename Type> inline void BinarySerializable<Type>::toBinary(std::ostream &outputStream) const
|
||||
template <typename Type, BinaryVersion v> inline void BinarySerializable<Type, v>::toBinary(std::ostream &outputStream, BinaryVersion version) const
|
||||
{
|
||||
BinaryReflector::BinarySerializer(&outputStream).write(static_cast<const Type &>(*this));
|
||||
BinaryReflector::BinarySerializer(&outputStream).write(static_cast<const Type &>(*this), version);
|
||||
}
|
||||
|
||||
template <typename Type> inline void BinarySerializable<Type>::restoreFromBinary(std::istream &inputStream)
|
||||
template <typename Type, BinaryVersion v> inline BinaryVersion BinarySerializable<Type, v>::restoreFromBinary(std::istream &inputStream)
|
||||
{
|
||||
BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast<Type &>(*this));
|
||||
return BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast<Type &>(*this));
|
||||
}
|
||||
|
||||
template <typename Type> Type BinarySerializable<Type>::fromBinary(std::istream &inputStream)
|
||||
template <typename Type, BinaryVersion v> Type BinarySerializable<Type, v>::fromBinary(std::istream &inputStream)
|
||||
{
|
||||
Type object;
|
||||
static_cast<BinarySerializable<Type> &>(object).restoreFromBinary(inputStream);
|
||||
|
@ -50,8 +58,7 @@ template <typename Type> Type BinarySerializable<Type>::fromBinary(std::istream
|
|||
* Find out whether this is a compiler bug or a correct error message.
|
||||
*/
|
||||
#define REFLECTIVE_RAPIDJSON_MAKE_BINARY_SERIALIZABLE(T) \
|
||||
template <> struct ReflectiveRapidJSON::AdaptedBinarySerializable<T> : Traits::Bool<true> { \
|
||||
}
|
||||
template <> struct ReflectiveRapidJSON::AdaptedBinarySerializable<T> : Traits::Bool<true> {}
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
# prevent multiple inclusion
|
||||
if (DEFINED REFLECTION_GENERATOR_MODULE_LOADED)
|
||||
|
@ -7,8 +7,10 @@ endif ()
|
|||
set(REFLECTION_GENERATOR_MODULE_LOADED YES)
|
||||
|
||||
# find code generator
|
||||
set(DEFAULT_REFLECTION_GENERATOR_EXECUTABLE "reflective_rapidjson_generator")
|
||||
set(REFLECTION_GENERATOR_EXECUTABLE "" CACHE FILEPATH "path to executable of reflection 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")
|
||||
if (REFLECTION_GENERATOR_EXECUTABLE)
|
||||
# use custom generator executable
|
||||
if (NOT EXISTS "${REFLECTION_GENERATOR_EXECUTABLE}" OR IS_DIRECTORY "${REFLECTION_GENERATOR_EXECUTABLE}")
|
||||
|
@ -25,46 +27,76 @@ if (NOT REFLECTION_GENERATOR_EXECUTABLE)
|
|||
message(
|
||||
FATAL_ERROR
|
||||
"Unable to find executable of generator for reflection code. Set REFLECTION_GENERATOR_EXECUTABLE to specify the path."
|
||||
)
|
||||
)
|
||||
endif ()
|
||||
|
||||
# determine Clang's resource directory
|
||||
set(REFLECTION_GENERATOR_CLANG_RESOURCE_DIR ""
|
||||
set(REFLECTION_GENERATOR_CLANG_RESOURCE_DIR
|
||||
""
|
||||
CACHE PATH "directory containing Clang's builtin headers, usually /usr/lib/clang/version")
|
||||
if (NOT REFLECTION_GENERATOR_CLANG_RESOURCE_DIR)
|
||||
if (NOT REFLECTION_GENERATOR_CLANG_BIN)
|
||||
find_program(REFLECTION_GENERATOR_CLANG_BIN clang NAMES clang++ PATHS "/usr/bin" "/bin")
|
||||
find_program(
|
||||
REFLECTION_GENERATOR_CLANG_BIN clang
|
||||
NAMES clang++
|
||||
PATHS "/usr/bin" "/bin")
|
||||
if (NOT REFLECTION_GENERATOR_CLANG_BIN)
|
||||
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(
|
||||
FATAL_ERROR
|
||||
"Unable to find Clang's resource directory. Set REFLECTION_GENERATOR_CLANG_RESOURCE_DIR manually to the corresponding path (usually /usr/lib/clang/\$version)."
|
||||
)
|
||||
)
|
||||
endif ()
|
||||
message(STATUS "Using ${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR} as Clang's resource directory for Reflective RapidJSON")
|
||||
|
||||
# allow to specify a custom include paths (useful for cross-compilation when header files are under custom prefix)
|
||||
set(REFLECTION_GENERATOR_INCLUDE_DIRECTORIES "" CACHE FILEPATH "include directories for code generator")
|
||||
set(REFLECTION_GENERATOR_INCLUDE_DIRECTORIES
|
||||
""
|
||||
CACHE FILEPATH "include directories for code generator")
|
||||
|
||||
# allow to specify a custom platform tiple (useful for cross-compilation to specify the target platform)
|
||||
set(REFLECTION_GENERATOR_TRIPLE "" CACHE STRING "platform triple for code generator")
|
||||
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
|
||||
|
@ -78,11 +110,7 @@ function (add_reflection_generator_invocation)
|
|||
|
||||
# apply specified REFLECTION_GENERATOR_TRIPLET
|
||||
if (REFLECTION_GENERATOR_TRIPLE)
|
||||
list(APPEND ARGS_CLANG_OPTIONS
|
||||
-Xclang
|
||||
-triple
|
||||
-Xclang
|
||||
"${REFLECTION_GENERATOR_TRIPLE}")
|
||||
list(APPEND ARGS_CLANG_OPTIONS -Xclang -triple -Xclang "${REFLECTION_GENERATOR_TRIPLE}")
|
||||
endif ()
|
||||
|
||||
# apply specified REFLECTION_GENERATOR_INCLUDE_DIRECTORIES
|
||||
|
@ -93,38 +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 CLANG_TIDY_CXX_FLAGS "-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 ()
|
||||
|
@ -151,12 +187,19 @@ function (add_reflection_generator_invocation)
|
|||
if (ARGS_JSON_VISIBILITY)
|
||||
list(APPEND CLI_ARGUMENTS --json-visibility "${ARGS_JSON_VISIBILITY}")
|
||||
endif ()
|
||||
add_custom_command(OUTPUT "${OUTPUT_FILE}"
|
||||
COMMAND "${REFLECTION_GENERATOR_EXECUTABLE}" ARGS ${CLI_ARGUMENTS}
|
||||
DEPENDS "${INPUT_FILE}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMENT "Generating reflection code for ${INPUT_FILE}"
|
||||
VERBATIM)
|
||||
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}
|
||||
DEPENDS "${INPUT_FILE}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMENT "Generating reflection code for ${INPUT_FILE}"
|
||||
VERBATIM)
|
||||
|
||||
# prevent Qt's code generator to be executed on the files generated by this code generator
|
||||
set_property(SOURCE "${OUTPUT_FILE}" PROPERTY SKIP_AUTOGEN ON)
|
||||
|
@ -165,7 +208,9 @@ function (add_reflection_generator_invocation)
|
|||
if (ARGS_OUTPUT_LISTS)
|
||||
foreach (OUTPUT_LIST ${ARGS_OUTPUT_LISTS})
|
||||
list(APPEND "${OUTPUT_LIST}" "${OUTPUT_FILE}")
|
||||
set("${OUTPUT_LIST}" "${${OUTPUT_LIST}}" PARENT_SCOPE)
|
||||
set("${OUTPUT_LIST}"
|
||||
"${${OUTPUT_LIST}}"
|
||||
PARENT_SCOPE)
|
||||
endforeach ()
|
||||
endif ()
|
||||
endforeach ()
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
#define REFLECTIVE_RAPIDJSON_EXPORT
|
||||
#define REFLECTIVE_RAPIDJSON_IMPORT
|
||||
#else
|
||||
#define REFLECTIVE_RAPIDJSON_EXPORT LIB_EXPORT
|
||||
#define REFLECTIVE_RAPIDJSON_IMPORT LIB_IMPORT
|
||||
#define REFLECTIVE_RAPIDJSON_EXPORT CPP_UTILITIES_GENERIC_LIB_EXPORT
|
||||
#define REFLECTIVE_RAPIDJSON_IMPORT CPP_UTILITIES_GENERIC_LIB_IMPORT
|
||||
#endif
|
||||
|
||||
/*!
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../../binary
|
|
@ -0,0 +1 @@
|
|||
../../global.h
|
|
@ -0,0 +1 @@
|
|||
../../traits.h
|
|
@ -0,0 +1 @@
|
|||
../../versioning.h
|
|
@ -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
|
|
@ -6,11 +6,11 @@
|
|||
* \brief Contains helper for error handling when deserializing JSON files.
|
||||
*/
|
||||
|
||||
#include <c++utilities/conversion/types.h>
|
||||
#include <c++utilities/misc/traits.h>
|
||||
|
||||
#include <rapidjson/rapidjson.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
@ -21,18 +21,20 @@ namespace ReflectiveRapidJSON {
|
|||
/*!
|
||||
* \brief The JsonDeserializationErrorKind enum specifies which kind of error happend when populating variables from parsing results.
|
||||
*/
|
||||
enum class JsonDeserializationErrorKind : byte {
|
||||
enum class JsonDeserializationErrorKind : std::uint8_t {
|
||||
TypeMismatch, /**< The expected type does not match the type actually present in the JSON document. */
|
||||
ArraySizeMismatch, /**< The expected array size does not match the actual size of the JSON array. A fixed size is expected when deserializing an std::tuple. */
|
||||
ConversionError, /**< The expected type matches the type present in the JSON document, but further conversion of the value failed. */
|
||||
UnexpectedDuplicate, /**< The expected type matches the type present in the JSON document, but the value can not be added to the container because it is already present and duplicates are not allowed. */
|
||||
InvalidVariantObject, /**< The present object is supposed to represent an std::variant but lacks the index or data member. */
|
||||
InvalidVariantIndex, /**< The present variant index is not a number of outside of the expected range. */
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The JsonType enum specifies the JSON data type.
|
||||
* \remarks This is currently only used for error handling to propagate expected and actual types in case of a mismatch.
|
||||
*/
|
||||
enum class JsonType : byte {
|
||||
enum class JsonType : std::uint8_t {
|
||||
Null,
|
||||
Number,
|
||||
Bool,
|
||||
|
@ -108,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;
|
||||
|
@ -121,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();
|
||||
};
|
||||
|
||||
|
@ -153,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);
|
||||
|
@ -164,7 +167,14 @@ struct JsonDeserializationErrors : public std::vector<JsonDeserializationError>
|
|||
/// \brief The index in the array which is currently processed.
|
||||
std::size_t currentIndex;
|
||||
/// \brief The list of fatal error types in form of flags.
|
||||
enum class ThrowOn : byte { None = 0, TypeMismatch = 0x1, ArraySizeMismatch = 0x2, ConversionError = 0x4, UnexpectedDuplicate = 0x8 } throwOn;
|
||||
enum class ThrowOn : std::uint8_t {
|
||||
None = 0,
|
||||
TypeMismatch = 0x1,
|
||||
ArraySizeMismatch = 0x2,
|
||||
ConversionError = 0x4,
|
||||
UnexpectedDuplicate = 0x8,
|
||||
All = 0xFF,
|
||||
} throwOn;
|
||||
|
||||
private:
|
||||
void throwMaybe(ThrowOn on) const;
|
||||
|
@ -186,7 +196,7 @@ inline JsonDeserializationErrors::JsonDeserializationErrors()
|
|||
*/
|
||||
constexpr JsonDeserializationErrors::ThrowOn operator|(JsonDeserializationErrors::ThrowOn lhs, JsonDeserializationErrors::ThrowOn rhs)
|
||||
{
|
||||
return static_cast<JsonDeserializationErrors::ThrowOn>(static_cast<byte>(lhs) | static_cast<byte>(rhs));
|
||||
return static_cast<JsonDeserializationErrors::ThrowOn>(static_cast<std::uint8_t>(lhs) | static_cast<std::uint8_t>(rhs));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -196,7 +206,7 @@ constexpr JsonDeserializationErrors::ThrowOn operator|(JsonDeserializationErrors
|
|||
*/
|
||||
inline void JsonDeserializationErrors::throwMaybe(ThrowOn on) const
|
||||
{
|
||||
if (static_cast<byte>(throwOn) & static_cast<byte>(on)) {
|
||||
if (static_cast<std::uint8_t>(throwOn) & static_cast<std::uint8_t>(on)) {
|
||||
throw back();
|
||||
}
|
||||
}
|
||||
|
@ -211,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.
|
||||
|
|
|
@ -20,16 +20,16 @@ namespace JsonReflector {
|
|||
// define functions to "push" values to a RapidJSON array or object
|
||||
|
||||
template <>
|
||||
inline void push<ChronoUtilities::DateTime>(
|
||||
const ChronoUtilities::DateTime &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
inline void push<CppUtilities::DateTime>(
|
||||
const CppUtilities::DateTime &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
const std::string str(reflectable.toIsoString());
|
||||
value.SetString(str.data(), rapidJsonSize(str.size()), allocator);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void push<ChronoUtilities::TimeSpan>(
|
||||
const ChronoUtilities::TimeSpan &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
inline void push<CppUtilities::TimeSpan>(
|
||||
const CppUtilities::TimeSpan &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
const std::string str(reflectable.toString());
|
||||
value.SetString(str.data(), rapidJsonSize(str.size()), allocator);
|
||||
|
@ -38,14 +38,14 @@ inline void push<ChronoUtilities::TimeSpan>(
|
|||
// define functions to "pull" values from a RapidJSON array or object
|
||||
|
||||
template <>
|
||||
inline void pull<ChronoUtilities::DateTime>(ChronoUtilities::DateTime &reflectable,
|
||||
inline void pull<CppUtilities::DateTime>(CppUtilities::DateTime &reflectable,
|
||||
const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
std::string str;
|
||||
pull(str, value, errors);
|
||||
try {
|
||||
reflectable = ChronoUtilities::DateTime::fromIsoStringGmt(str.data());
|
||||
} catch (const ConversionUtilities::ConversionException &) {
|
||||
reflectable = CppUtilities::DateTime::fromIsoStringGmt(str.data());
|
||||
} catch (const CppUtilities::ConversionException &) {
|
||||
if (errors) {
|
||||
errors->reportConversionError(JsonType::String);
|
||||
}
|
||||
|
@ -53,14 +53,14 @@ inline void pull<ChronoUtilities::DateTime>(ChronoUtilities::DateTime &reflectab
|
|||
}
|
||||
|
||||
template <>
|
||||
inline void pull<ChronoUtilities::TimeSpan>(ChronoUtilities::TimeSpan &reflectable,
|
||||
inline void pull<CppUtilities::TimeSpan>(CppUtilities::TimeSpan &reflectable,
|
||||
const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
std::string str;
|
||||
pull(str, value, errors);
|
||||
try {
|
||||
reflectable = ChronoUtilities::TimeSpan::fromString(str.data());
|
||||
} catch (const ConversionUtilities::ConversionException &) {
|
||||
reflectable = CppUtilities::TimeSpan::fromString(str.data());
|
||||
} catch (const CppUtilities::ConversionException &) {
|
||||
if (errors) {
|
||||
errors->reportConversionError(JsonType::String);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
#include "../traits.h"
|
||||
|
||||
#include <c++utilities/conversion/types.h>
|
||||
#include <c++utilities/application/global.h>
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/rapidjson.h>
|
||||
|
@ -17,9 +17,16 @@
|
|||
#include <rapidjson/writer.h>
|
||||
|
||||
#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"
|
||||
|
||||
|
@ -76,8 +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::IsIteratable<Type>, Traits::IsSpecializationOf<Type, std::unique_ptr>,
|
||||
Traits::IsSpecializationOf<Type, std::shared_ptr>, Traits::IsSpecializationOf<Type, std::weak_ptr>>;
|
||||
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
|
||||
|
@ -141,37 +148,58 @@ 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.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_enum<Type>> * = nullptr>
|
||||
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
value.Set(static_cast<Traits::Conditional<std::is_unsigned<typename std::underlying_type<Type>::type>, uint64, int64>>(reflectable), allocator);
|
||||
value.Set(static_cast<Traits::Conditional<std::is_unsigned<typename std::underlying_type<Type>::type>, std::uint64_t, std::int64_t>>(reflectable),
|
||||
allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \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();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -180,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);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -191,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);
|
||||
}
|
||||
|
@ -211,10 +239,9 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified map (std::map, std::unordered_map) or multimap (std::multimap, std::unordered_multimap) to the
|
||||
* specified value.
|
||||
* \brief Pushes the specified map (std::map, std::unordered_map) to the specified value.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr>
|
||||
template <typename Type, Traits::EnableIfAny<IsMapOrHash<Type>> * = nullptr>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
value.SetObject();
|
||||
|
@ -224,6 +251,27 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified multimap (std::multimap, std::unordered_multimap) to the specified value.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<IsMultiMapOrHash<Type>> * = nullptr>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
value.SetObject();
|
||||
for (const auto &item : reflectable) {
|
||||
const auto memberName = RAPIDJSON_NAMESPACE::Value::StringRefType(item.first.data(), rapidJsonSize(item.first.size()));
|
||||
const auto existingMember = value.FindMember(memberName);
|
||||
const auto arrayAlreadyExists
|
||||
= existingMember != value.MemberEnd() && existingMember->value.GetType() == RAPIDJSON_NAMESPACE::Type::kArrayType;
|
||||
auto newArrayValue = RAPIDJSON_NAMESPACE::Value{ RAPIDJSON_NAMESPACE::kArrayType };
|
||||
RAPIDJSON_NAMESPACE::Value::Array array = arrayAlreadyExists ? existingMember->value.GetArray() : newArrayValue.GetArray();
|
||||
push(item.second, array, allocator);
|
||||
if (!arrayAlreadyExists) {
|
||||
value.AddMember(memberName, newArrayValue, allocator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Detail {
|
||||
|
||||
/*!
|
||||
|
@ -258,11 +306,23 @@ 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 pair to the specified value.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
value.SetArray();
|
||||
RAPIDJSON_NAMESPACE::Value::Array array(value.GetArray());
|
||||
array.Reserve(2, allocator);
|
||||
push(reflectable.first, array, allocator);
|
||||
push(reflectable.second, array, allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \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) {
|
||||
|
@ -272,6 +332,35 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
|
|||
push(*reflectable, value, allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified variant to the specified value.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
if (reflectable.valueless_by_exception()) {
|
||||
value.SetNull();
|
||||
return;
|
||||
}
|
||||
|
||||
RAPIDJSON_NAMESPACE::Value index, data;
|
||||
index.SetUint64(reflectable.index());
|
||||
std::visit(
|
||||
[&data, &allocator](const auto &reflectableOfActualType) {
|
||||
if constexpr (!std::is_same_v<std::decay_t<decltype(reflectableOfActualType)>, std::monostate>) {
|
||||
push(reflectableOfActualType, data, allocator);
|
||||
} else {
|
||||
CPP_UTILITIES_UNUSED(data)
|
||||
CPP_UTILITIES_UNUSED(allocator)
|
||||
}
|
||||
},
|
||||
reflectable);
|
||||
|
||||
value.SetObject();
|
||||
value.AddMember("index", index, allocator);
|
||||
value.AddMember("data", data, allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified \a reflectable which has a custom type to the specified array.
|
||||
*/
|
||||
|
@ -384,6 +473,12 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
|
|||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>> * = nullptr>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a pair from the specified value which is checked to contain an array.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a unique_ptr from the specified value which might be null.
|
||||
*/
|
||||
|
@ -396,6 +491,18 @@ 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.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable from the specified value iterator which is checked to contain the right type.
|
||||
*/
|
||||
|
@ -422,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)
|
||||
{
|
||||
|
@ -435,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.
|
||||
*/
|
||||
|
@ -459,7 +581,7 @@ template <typename Type, Traits::EnableIfAny<std::is_enum<Type>> * = nullptr>
|
|||
inline void pull(
|
||||
Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
using ExpectedType = Traits::Conditional<std::is_unsigned<typename std::underlying_type<Type>::type>, uint64, int64>;
|
||||
using ExpectedType = Traits::Conditional<std::is_unsigned<typename std::underlying_type<Type>::type>, std::uint64_t, std::int64_t>;
|
||||
if (!value.Is<ExpectedType>()) {
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<ExpectedType>(value.GetType());
|
||||
|
@ -489,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()) {
|
||||
|
@ -578,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
|
||||
|
@ -606,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);
|
||||
}
|
||||
}
|
||||
|
@ -649,8 +772,16 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
|
|||
}
|
||||
auto obj = value.GetObject();
|
||||
for (auto i = obj.MemberBegin(), end = obj.MemberEnd(); i != end; ++i) {
|
||||
auto insertedIterator = reflectable.insert(typename Type::value_type(i->name.GetString(), typename Type::mapped_type()));
|
||||
pull(insertedIterator->second, i->value, errors);
|
||||
if (i->value.GetType() != RAPIDJSON_NAMESPACE::kArrayType) {
|
||||
auto insertedIterator = reflectable.insert(typename Type::value_type(i->name.GetString(), typename Type::mapped_type()));
|
||||
pull(insertedIterator->second, i->value, errors);
|
||||
continue;
|
||||
}
|
||||
const auto array = i->value.GetArray();
|
||||
for (const auto &arrayValue : array) {
|
||||
auto insertedIterator = reflectable.insert(typename Type::value_type(i->name.GetString(), typename Type::mapped_type()));
|
||||
pull(insertedIterator->second, arrayValue, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -688,7 +819,7 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
|
|||
}
|
||||
return;
|
||||
}
|
||||
auto array = value.GetArray();
|
||||
const auto array = value.GetArray();
|
||||
if (array.Size() != std::tuple_size<Type>::value) {
|
||||
if (errors) {
|
||||
// FIXME: report expected and actual size
|
||||
|
@ -699,6 +830,30 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
|
|||
Detail::TuplePullHelper<Type, std::tuple_size<Type>::value>::pull(reflectable, array, errors);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a pair from the specified value which is checked to contain an array.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> *>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
if (!value.IsArray()) {
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(value.GetType());
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto array = value.GetArray();
|
||||
if (array.Size() != 2) {
|
||||
if (errors) {
|
||||
// FIXME: report expected and actual size
|
||||
errors->reportArraySizeMismatch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
pull(reflectable.first, array[0], errors);
|
||||
pull(reflectable.second, array[1], errors);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a unique_ptr from the specified value which might be null.
|
||||
*/
|
||||
|
@ -727,6 +882,80 @@ 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>
|
||||
void assignVariantValueByRuntimeIndex(std::size_t runtimeIndex, Variant &variant,
|
||||
const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
if constexpr (compiletimeIndex < std::variant_size_v<Variant>) {
|
||||
if (compiletimeIndex == runtimeIndex) {
|
||||
if constexpr (std::is_same_v<std::variant_alternative_t<compiletimeIndex, Variant>, std::monostate>) {
|
||||
variant = std::monostate{};
|
||||
} else {
|
||||
pull(variant.template emplace<compiletimeIndex>(), value, errors);
|
||||
}
|
||||
} else {
|
||||
assignVariantValueByRuntimeIndex<Variant, compiletimeIndex + 1>(runtimeIndex, variant, value, errors);
|
||||
}
|
||||
} else {
|
||||
if (errors) {
|
||||
errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantIndex, JsonType::Number, JsonType::Number, errors->currentRecord,
|
||||
errors->currentMember, errors->currentIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace Detail
|
||||
/// \endcond
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a variant from the specified value which might be null.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsVariant<Type>> *>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
if (!value.IsObject()) {
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(value.GetType());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto obj = value.GetObject();
|
||||
auto indexIterator = obj.FindMember("index");
|
||||
auto dataIterator = obj.FindMember("data");
|
||||
if (indexIterator == obj.MemberEnd() || dataIterator == obj.MemberEnd()) {
|
||||
if (errors) {
|
||||
errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantObject, JsonType::Object, JsonType::Object, errors->currentRecord,
|
||||
errors->currentMember, errors->currentIndex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto &indexValue = indexIterator->value;
|
||||
if (!indexValue.IsUint64()) {
|
||||
if (errors) {
|
||||
errors->emplace_back(JsonDeserializationErrorKind::InvalidVariantIndex, JsonType::Number, jsonType(indexValue.GetType()),
|
||||
errors->currentRecord, errors->currentMember, errors->currentIndex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Detail::assignVariantValueByRuntimeIndex(indexValue.GetUint64(), reflectable, dataIterator->value, errors);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable from the specified value iterator which is checked to contain the right type.
|
||||
*/
|
||||
|
@ -753,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;
|
||||
|
@ -792,8 +1021,7 @@ template <typename Type, Traits::EnableIfAny<IsJsonSerializable<Type>, IsMapOrHa
|
|||
RAPIDJSON_NAMESPACE::Document toJsonDocument(const Type &reflectable)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kObjectType);
|
||||
RAPIDJSON_NAMESPACE::Document::Object object(document.GetObject());
|
||||
push(reflectable, object, document.GetAllocator());
|
||||
push(reflectable, document, document.GetAllocator());
|
||||
return document;
|
||||
}
|
||||
|
||||
|
@ -830,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.
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <c++utilities/io/misc.h>
|
||||
#include <c++utilities/tests/testutils.h>
|
||||
|
||||
using TestUtilities::operator<<; // must be visible prior to the call site
|
||||
using CppUtilities::operator<<; // must be visible prior to the call site
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
|
@ -21,10 +21,8 @@ using TestUtilities::operator<<; // must be visible prior to the call site
|
|||
using namespace std;
|
||||
using namespace CPPUNIT_NS;
|
||||
using namespace RAPIDJSON_NAMESPACE;
|
||||
using namespace IoUtilities;
|
||||
using namespace ConversionUtilities;
|
||||
using namespace TestUtilities;
|
||||
using namespace TestUtilities::Literals;
|
||||
using namespace CppUtilities;
|
||||
using namespace CppUtilities::Literals;
|
||||
using namespace ReflectiveRapidJSON;
|
||||
|
||||
/// \cond
|
||||
|
@ -50,8 +48,8 @@ class BinaryReflectorBoostHanaTests : public TestFixture {
|
|||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown();
|
||||
void setUp() override;
|
||||
void tearDown() override;
|
||||
|
||||
void testSerializingAndDeserializing();
|
||||
|
||||
|
|
|
@ -7,10 +7,11 @@
|
|||
#include <c++utilities/io/misc.h>
|
||||
#include <c++utilities/tests/testutils.h>
|
||||
|
||||
using TestUtilities::operator<<; // must be visible prior to the call site
|
||||
using CppUtilities::operator<<; // must be visible prior to the call site
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
|
@ -22,23 +23,20 @@ using TestUtilities::operator<<; // must be visible prior to the call site
|
|||
|
||||
using namespace std;
|
||||
using namespace CPPUNIT_NS;
|
||||
using namespace IoUtilities;
|
||||
using namespace ChronoUtilities;
|
||||
using namespace ConversionUtilities;
|
||||
using namespace TestUtilities;
|
||||
using namespace TestUtilities::Literals;
|
||||
using namespace CppUtilities;
|
||||
using namespace CppUtilities::Literals;
|
||||
using namespace ReflectiveRapidJSON;
|
||||
|
||||
/// \cond
|
||||
|
||||
// define some enums and structs for testing serialization
|
||||
enum SomeEnum {
|
||||
enum SomeEnumBinary {
|
||||
SomeEnumItem1,
|
||||
SomeEnumItem2,
|
||||
SomeEnumItem3,
|
||||
};
|
||||
|
||||
enum class SomeEnumClass : uint16 {
|
||||
enum class SomeEnumClassBinary : std::uint16_t {
|
||||
Item1,
|
||||
Item2,
|
||||
Item3,
|
||||
|
@ -56,8 +54,8 @@ struct TestObjectBinary : public BinarySerializable<TestObjectBinary> {
|
|||
multiset<string> someMultiset;
|
||||
unordered_set<string> someUnorderedSet;
|
||||
unordered_multiset<string> someUnorderedMultiset;
|
||||
SomeEnum someEnum;
|
||||
SomeEnumClass someEnumClass;
|
||||
SomeEnumBinary someEnum;
|
||||
SomeEnumClassBinary someEnumClass;
|
||||
TimeSpan timeSpan;
|
||||
DateTime dateTime;
|
||||
};
|
||||
|
@ -67,12 +65,19 @@ struct NestingArrayBinary : public BinarySerializable<NestingArrayBinary> {
|
|||
vector<TestObjectBinary> testObjects;
|
||||
};
|
||||
|
||||
struct ObjectWithVariantsBinary : public BinarySerializable<ObjectWithVariantsBinary> {
|
||||
variant<int, string, monostate> someVariant;
|
||||
variant<string, float> anotherVariant;
|
||||
variant<string, int> yetAnotherVariant;
|
||||
};
|
||||
|
||||
// pretend serialization code for structs has been generated
|
||||
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);
|
||||
|
@ -88,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);
|
||||
|
@ -109,18 +116,40 @@ 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 <>
|
||||
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, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
serializer.write(customType.someVariant);
|
||||
serializer.write(customType.anotherVariant);
|
||||
serializer.write(customType.yetAnotherVariant);
|
||||
}
|
||||
|
||||
} // namespace BinaryReflector
|
||||
|
||||
// namespace BinaryReflector
|
||||
|
@ -140,13 +169,15 @@ class BinaryReflectorTests : public TestFixture {
|
|||
CPPUNIT_TEST(testDeserializeNestedStruct);
|
||||
CPPUNIT_TEST(testSmallSharedPointer);
|
||||
CPPUNIT_TEST(testBigSharedPointer);
|
||||
CPPUNIT_TEST(testVariant);
|
||||
CPPUNIT_TEST(testOptional);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
BinaryReflectorTests();
|
||||
|
||||
void setUp();
|
||||
void tearDown();
|
||||
void setUp() override;
|
||||
void tearDown() override;
|
||||
|
||||
void testSerializeSimpleStruct();
|
||||
void testDeserializeSimpleStruct();
|
||||
|
@ -156,6 +187,8 @@ public:
|
|||
void testSharedPointer(std::uintptr_t fakePointer);
|
||||
void testSmallSharedPointer();
|
||||
void testBigSharedPointer();
|
||||
void testVariant();
|
||||
void testOptional();
|
||||
|
||||
private:
|
||||
vector<unsigned char> m_buffer;
|
||||
|
@ -226,7 +259,7 @@ void BinaryReflectorTests::setUp()
|
|||
m_testObj.someSet = { "1", "2", "3", "2" };
|
||||
m_testObj.someMultiset = { "1", "2", "3", "2" };
|
||||
m_testObj.someEnum = SomeEnumItem2;
|
||||
m_testObj.someEnumClass = SomeEnumClass::Item3;
|
||||
m_testObj.someEnumClass = SomeEnumClassBinary::Item3;
|
||||
m_testObj.timeSpan = TimeSpan(0xABCD);
|
||||
m_testObj.dateTime = DateTime(0xEFAB);
|
||||
m_nestedTestObj.name = "struct with nesting";
|
||||
|
@ -240,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);
|
||||
}
|
||||
|
@ -255,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);
|
||||
}
|
||||
|
@ -265,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);
|
||||
}
|
||||
|
@ -275,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);
|
||||
|
@ -317,7 +382,7 @@ void BinaryReflectorTests::testSharedPointer(uintptr_t fakePointer)
|
|||
BinaryReflector::BinaryDeserializer deserializer(&stream);
|
||||
shared_ptr<int> readPtr;
|
||||
deserializer.m_pointer[fakePointer] = "foo";
|
||||
CPPUNIT_ASSERT_THROW(deserializer.read(readPtr), ConversionUtilities::ConversionException);
|
||||
CPPUNIT_ASSERT_THROW(deserializer.read(readPtr), CppUtilities::ConversionException);
|
||||
CPPUNIT_ASSERT(readPtr == nullptr);
|
||||
|
||||
// deserialize the shared pointer assuming it has already been read and the type matches
|
||||
|
@ -337,3 +402,51 @@ void BinaryReflectorTests::testBigSharedPointer()
|
|||
{
|
||||
testSharedPointer(std::numeric_limits<std::uintptr_t>::max());
|
||||
}
|
||||
|
||||
void BinaryReflectorTests::testVariant()
|
||||
{
|
||||
// create test object
|
||||
ObjectWithVariantsBinary variants;
|
||||
variants.someVariant = std::monostate{};
|
||||
variants.anotherVariant = "foo";
|
||||
variants.yetAnotherVariant = 42;
|
||||
|
||||
// serialize test object
|
||||
stringstream stream(ios_base::in | ios_base::out | ios_base::binary);
|
||||
stream.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
variants.toBinary(stream);
|
||||
|
||||
// deserialize the object again
|
||||
const auto deserializedVariants = ObjectWithVariantsBinary::fromBinary(stream);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(2_st, deserializedVariants.someVariant.index());
|
||||
CPPUNIT_ASSERT_EQUAL(0_st, deserializedVariants.anotherVariant.index());
|
||||
CPPUNIT_ASSERT_EQUAL(1_st, deserializedVariants.yetAnotherVariant.index());
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <c++utilities/io/misc.h>
|
||||
#include <c++utilities/tests/testutils.h>
|
||||
|
||||
using TestUtilities::operator<<; // must be visible prior to the call site
|
||||
using CppUtilities::operator<<; // must be visible prior to the call site
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
|
@ -21,10 +21,8 @@ using TestUtilities::operator<<; // must be visible prior to the call site
|
|||
using namespace std;
|
||||
using namespace CPPUNIT_NS;
|
||||
using namespace RAPIDJSON_NAMESPACE;
|
||||
using namespace IoUtilities;
|
||||
using namespace ConversionUtilities;
|
||||
using namespace TestUtilities;
|
||||
using namespace TestUtilities::Literals;
|
||||
using namespace CppUtilities;
|
||||
using namespace CppUtilities::Literals;
|
||||
using namespace ReflectiveRapidJSON;
|
||||
|
||||
/// \cond
|
||||
|
@ -58,8 +56,8 @@ class JsonReflectorBoostHanaTests : public TestFixture {
|
|||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown();
|
||||
void setUp() override;
|
||||
void tearDown() override;
|
||||
|
||||
void testSerializePrimitives();
|
||||
void testSerializeSimpleObjects();
|
||||
|
@ -162,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
using namespace std;
|
||||
using namespace CPPUNIT_NS;
|
||||
using namespace RAPIDJSON_NAMESPACE;
|
||||
using namespace ChronoUtilities;
|
||||
using namespace TestUtilities::Literals;
|
||||
using namespace CppUtilities;
|
||||
using namespace CppUtilities::Literals;
|
||||
using namespace ReflectiveRapidJSON;
|
||||
|
||||
/*!
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <c++utilities/io/misc.h>
|
||||
#include <c++utilities/tests/testutils.h>
|
||||
|
||||
using TestUtilities::operator<<; // must be visible prior to the call site
|
||||
using CppUtilities::operator<<; // must be visible prior to the call site
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
|
@ -24,10 +24,8 @@ using TestUtilities::operator<<; // must be visible prior to the call site
|
|||
using namespace std;
|
||||
using namespace CPPUNIT_NS;
|
||||
using namespace RAPIDJSON_NAMESPACE;
|
||||
using namespace IoUtilities;
|
||||
using namespace ConversionUtilities;
|
||||
using namespace TestUtilities;
|
||||
using namespace TestUtilities::Literals;
|
||||
using namespace CppUtilities;
|
||||
using namespace CppUtilities::Literals;
|
||||
using namespace ReflectiveRapidJSON;
|
||||
|
||||
/// \cond
|
||||
|
@ -54,10 +52,15 @@ struct TestObject : public JsonSerializable<TestObject> {
|
|||
bool boolean;
|
||||
map<string, int> someMap;
|
||||
unordered_map<string, bool> someHash;
|
||||
multimap<string, int> someMultimap;
|
||||
unordered_multimap<string, int> someMultiHash;
|
||||
set<string> someSet;
|
||||
multiset<string> someMultiset;
|
||||
unordered_set<string> someUnorderedSet;
|
||||
unordered_multiset<string> someUnorderedMultiset;
|
||||
variant<monostate, string, int, float> someVariant;
|
||||
variant<string, int, float> anotherVariant;
|
||||
variant<string, int, float> yetAnotherVariant;
|
||||
};
|
||||
|
||||
struct NestingObject : public JsonSerializable<NestingObject> {
|
||||
|
@ -83,10 +86,15 @@ template <> inline void push<TestObject>(const TestObject &reflectable, Value::O
|
|||
push(reflectable.boolean, "boolean", value, allocator);
|
||||
push(reflectable.someMap, "someMap", value, allocator);
|
||||
push(reflectable.someHash, "someHash", value, allocator);
|
||||
push(reflectable.someMultimap, "someMultimap", value, allocator);
|
||||
push(reflectable.someMultiHash, "someMultiHash", value, allocator);
|
||||
push(reflectable.someSet, "someSet", value, allocator);
|
||||
push(reflectable.someMultiset, "someMultiset", value, allocator);
|
||||
push(reflectable.someUnorderedSet, "someUnorderedSet", value, allocator);
|
||||
push(reflectable.someUnorderedMultiset, "someUnorderedMultiset", value, allocator);
|
||||
push(reflectable.someVariant, "someVariant", value, allocator);
|
||||
push(reflectable.anotherVariant, "anotherVariant", value, allocator);
|
||||
push(reflectable.yetAnotherVariant, "yetAnotherVariant", value, allocator);
|
||||
}
|
||||
|
||||
template <> inline void push<NestingObject>(const NestingObject &reflectable, Value::Object &value, Document::AllocatorType &allocator)
|
||||
|
@ -104,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";
|
||||
|
@ -116,10 +124,15 @@ inline void pull<TestObject>(TestObject &reflectable, const GenericValue<UTF8<ch
|
|||
pull(reflectable.boolean, "boolean", value, errors);
|
||||
pull(reflectable.someMap, "someMap", value, errors);
|
||||
pull(reflectable.someHash, "someHash", value, errors);
|
||||
pull(reflectable.someMultimap, "someMultimap", value, errors);
|
||||
pull(reflectable.someMultiHash, "someMultiHash", value, errors);
|
||||
pull(reflectable.someSet, "someSet", value, errors);
|
||||
pull(reflectable.someMultiset, "someMultiset", value, errors);
|
||||
pull(reflectable.someUnorderedSet, "someUnorderedSet", value, errors);
|
||||
pull(reflectable.someUnorderedMultiset, "someUnorderedMultiset", value, errors);
|
||||
pull(reflectable.someVariant, "someVariant", value, errors);
|
||||
pull(reflectable.anotherVariant, "anotherVariant", value, errors);
|
||||
pull(reflectable.yetAnotherVariant, "yetAnotherVariant", value, errors);
|
||||
if (errors) {
|
||||
errors->currentRecord = previousRecord;
|
||||
}
|
||||
|
@ -128,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";
|
||||
|
@ -143,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";
|
||||
|
@ -173,18 +186,20 @@ 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();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown();
|
||||
void setUp() override;
|
||||
void tearDown() override;
|
||||
|
||||
void experiment();
|
||||
void testSerializePrimitives();
|
||||
|
@ -192,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();
|
||||
|
||||
|
@ -224,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
|
||||
|
@ -263,12 +280,17 @@ void JsonReflectorTests::testSerializeSimpleObjects()
|
|||
testObj.boolean = false;
|
||||
testObj.someMap = { { "a", 1 }, { "b", 2 } };
|
||||
testObj.someHash = { { "c", true }, { "d", false } };
|
||||
testObj.someMultimap = { { "a", 1 }, { "a", 2 }, { "b", 3 } };
|
||||
testObj.someMultiHash = { { "a", 1 } };
|
||||
testObj.someSet = { "a", "b", "c" };
|
||||
testObj.someMultiset = { "a", "b", "b" };
|
||||
testObj.someUnorderedSet = { "a" };
|
||||
testObj.someUnorderedMultiset = { "b", "b", "b" };
|
||||
testObj.someVariant = std::monostate{};
|
||||
testObj.anotherVariant = "foo";
|
||||
testObj.yetAnotherVariant = 42;
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
"{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"d\":false,\"c\":true},\"someSet\":[\"a\",\"b\",\"c\"],\"someMultiset\":[\"a\",\"b\",\"b\"],\"someUnorderedSet\":[\"a\"],\"someUnorderedMultiset\":[\"b\",\"b\",\"b\"]}"s,
|
||||
"{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"d\":false,\"c\":true},\"someMultimap\":{\"a\":[1,2],\"b\":[3]},\"someMultiHash\":{\"a\":[1]},\"someSet\":[\"a\",\"b\",\"c\"],\"someMultiset\":[\"a\",\"b\",\"b\"],\"someUnorderedSet\":[\"a\"],\"someUnorderedMultiset\":[\"b\",\"b\",\"b\"],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"foo\"},\"yetAnotherVariant\":{\"index\":1,\"data\":42}}"s,
|
||||
string(testObj.toJson().GetString()));
|
||||
}
|
||||
|
||||
|
@ -286,7 +308,7 @@ void JsonReflectorTests::testSerializeNestedObjects()
|
|||
testObj.text = "test";
|
||||
testObj.boolean = false;
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
"{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}}"s,
|
||||
"{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}}"s,
|
||||
string(nestingObj.toJson().GetString()));
|
||||
|
||||
NestingArray nestingArray;
|
||||
|
@ -295,13 +317,13 @@ void JsonReflectorTests::testSerializeNestedObjects()
|
|||
nestingArray.testObjects.emplace_back(testObj);
|
||||
nestingArray.testObjects.back().number = 43;
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
"{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]},{\"number\":43,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}]}"s,
|
||||
"{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}},{\"number\":43,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]}"s,
|
||||
string(nestingArray.toJson().GetString()));
|
||||
|
||||
vector<TestObject> nestedInVector;
|
||||
nestedInVector.emplace_back(testObj);
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
"[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}]"s,
|
||||
"[{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s,
|
||||
string(JsonReflector::toJson(nestedInVector).GetString()));
|
||||
}
|
||||
|
||||
|
@ -329,7 +351,7 @@ void JsonReflectorTests::testSerializeUniquePtr()
|
|||
Writer<StringBuffer> jsonWriter(strbuf);
|
||||
doc.Accept(jsonWriter);
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
"[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}]"s,
|
||||
"[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s,
|
||||
string(strbuf.GetString()));
|
||||
}
|
||||
|
||||
|
@ -357,10 +379,32 @@ void JsonReflectorTests::testSerializeSharedPtr()
|
|||
Writer<StringBuffer> jsonWriter(strbuf);
|
||||
doc.Accept(jsonWriter);
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
"[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[]}]"s,
|
||||
"[\"foo\",null,{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"bar\",\"boolean\":false,\"someMap\":{},\"someHash\":{},\"someMultimap\":{},\"someMultiHash\":{},\"someSet\":[],\"someMultiset\":[],\"someUnorderedSet\":[],\"someUnorderedMultiset\":[],\"someVariant\":{\"index\":0,\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"\"},\"yetAnotherVariant\":{\"index\":0,\"data\":\"\"}}]"s,
|
||||
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.
|
||||
*/
|
||||
|
@ -422,10 +466,13 @@ void JsonReflectorTests::testDeserializePrimitives()
|
|||
*/
|
||||
void JsonReflectorTests::testDeserializeSimpleObjects()
|
||||
{
|
||||
const TestObject testObj(
|
||||
TestObject::fromJson("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":"
|
||||
"false,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"c\":true,\"d\":false},\"someSet\":[\"a\",\"b\"],\"someMultiset\":["
|
||||
"\"a\",\"a\"],\"someUnorderedSet\":[\"a\",\"b\"],\"someUnorderedMultiset\":[\"a\",\"a\"]}"));
|
||||
const auto testObj
|
||||
= TestObject::fromJson("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":"
|
||||
"false,\"someMap\":{\"a\":1,\"b\":2},\"someHash\":{\"c\":true,\"d\":false},\"someMultimap\":{\"a\":[1,2],\"b\":[3]},"
|
||||
"\"someMultiHash\":{\"a\":[4,5],\"b\":[6]},"
|
||||
"\"someSet\":[\"a\",\"b\"],\"someMultiset\":["
|
||||
"\"a\",\"a\"],\"someUnorderedSet\":[\"a\",\"b\"],\"someUnorderedMultiset\":[\"a\",\"a\"],\"someVariant\":{\"index\":0,"
|
||||
"\"data\":null},\"anotherVariant\":{\"index\":0,\"data\":\"foo\"},\"yetAnotherVariant\":{\"index\":1,\"data\":42}}");
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(42, testObj.number);
|
||||
CPPUNIT_ASSERT_EQUAL(3.141592653589793, testObj.number2);
|
||||
|
@ -436,10 +483,19 @@ void JsonReflectorTests::testDeserializeSimpleObjects()
|
|||
CPPUNIT_ASSERT_EQUAL(expectedMap, testObj.someMap);
|
||||
const unordered_map<string, bool> expectedHash{ { "c", true }, { "d", false } };
|
||||
CPPUNIT_ASSERT_EQUAL(expectedHash, testObj.someHash);
|
||||
const multimap<string, int> expectedMultiMap{ { "a", 1 }, { "a", 2 }, { "b", 3 } };
|
||||
CPPUNIT_ASSERT_EQUAL(expectedMultiMap, testObj.someMultimap);
|
||||
const unordered_multimap<string, int> expectedUnorderedMultiMap{ { "a", 4 }, { "a", 5 }, { "b", 6 } };
|
||||
CPPUNIT_ASSERT_EQUAL(expectedUnorderedMultiMap, testObj.someMultiHash);
|
||||
CPPUNIT_ASSERT_EQUAL(set<string>({ "a", "b" }), testObj.someSet);
|
||||
CPPUNIT_ASSERT_EQUAL(multiset<string>({ "a", "a" }), testObj.someMultiset);
|
||||
CPPUNIT_ASSERT_EQUAL(unordered_set<string>({ "a", "b" }), testObj.someUnorderedSet);
|
||||
CPPUNIT_ASSERT_EQUAL(unordered_multiset<string>({ "a", "a" }), testObj.someUnorderedMultiset);
|
||||
CPPUNIT_ASSERT_EQUAL(0_st, testObj.someVariant.index());
|
||||
CPPUNIT_ASSERT_EQUAL(0_st, testObj.anotherVariant.index());
|
||||
CPPUNIT_ASSERT_EQUAL("foo"s, std::get<0>(testObj.anotherVariant));
|
||||
CPPUNIT_ASSERT_EQUAL(1_st, testObj.yetAnotherVariant.index());
|
||||
CPPUNIT_ASSERT_EQUAL(42, std::get<1>(testObj.yetAnotherVariant));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -470,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>>(
|
||||
|
@ -487,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);
|
||||
|
@ -531,6 +590,22 @@ void JsonReflectorTests::testDeserializeSharedPtr()
|
|||
CPPUNIT_ASSERT_EQUAL("bar"s, obj->text);
|
||||
}
|
||||
|
||||
void JsonReflectorTests::testDeserializeOptional()
|
||||
{
|
||||
Document doc(kArrayType);
|
||||
doc.Parse("[\"foo\",null]");
|
||||
auto array = doc.GetArray().begin();
|
||||
|
||||
optional<string> str = "foo"s;
|
||||
optional<string> nullStr;
|
||||
JsonDeserializationErrors errors;
|
||||
JsonReflector::pull(str, array, &errors);
|
||||
CPPUNIT_ASSERT_EQUAL(0_st, errors.size());
|
||||
CPPUNIT_ASSERT(str.has_value());
|
||||
CPPUNIT_ASSERT_EQUAL("foo"s, *str);
|
||||
CPPUNIT_ASSERT(!nullStr.has_value());
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests whether RAPIDJSON_NAMESPACE::ParseResult is thrown correctly when passing invalid JSON to fromJSON().
|
||||
*/
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
#include "../traits.h"
|
||||
#include "../versioning.h"
|
||||
|
||||
#include "../binary/serializable.h"
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
// treat some types differently to test Treat... traits
|
||||
struct Foo {
|
||||
};
|
||||
struct Bar {
|
||||
};
|
||||
// define structs for testing REFLECTIVE_RAPIDJSON_TREAT_AS_…
|
||||
struct Foo {};
|
||||
struct Bar {};
|
||||
|
||||
// define structs for testing versioning
|
||||
struct VersionlessBase : public ReflectiveRapidJSON::BinarySerializable<VersionlessBase> {};
|
||||
struct VersionedDerived : public VersionlessBase, public ReflectiveRapidJSON::BinarySerializable<VersionedDerived, 1> {};
|
||||
struct VersionedBase : public ReflectiveRapidJSON::BinarySerializable<VersionlessBase, 1> {};
|
||||
struct VersionlessDerived : public VersionedBase, public ReflectiveRapidJSON::BinarySerializable<VersionlessDerived> {};
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH(Foo);
|
||||
REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH(Foo);
|
||||
|
@ -48,3 +56,14 @@ static_assert(IsIteratableExceptString<std::vector<int>>::value, "vector is iter
|
|||
static_assert(!IsIteratableExceptString<std::string>::value, "string not iteratable");
|
||||
static_assert(!IsIteratableExceptString<std::wstring>::value, "wstring not iteratable");
|
||||
static_assert(!IsIteratableExceptString<const std::string>::value, "string not iteratable");
|
||||
|
||||
// test versioning traits
|
||||
static_assert(!Versioning<int>::enabled, "versioning for built-in types not enabled");
|
||||
static_assert(!Versioning<std::string>::enabled, "versioning for standard types not enabled");
|
||||
static_assert(!Versioning<VersionlessBase>::enabled, "versioning not enabled by default");
|
||||
static_assert(Versioning<BinarySerializable<VersionedDerived, 1>>::enabled, "versioning enabled if non-zero version parameter specified (derived)");
|
||||
static_assert(Versioning<VersionedBase>::enabled, "versioning enabled if non-zero version parameter specified (base)");
|
||||
static_assert(!Versioning<BinarySerializable<VersionlessDerived>>::enabled, "versioning disabled for derived, even if base is versioned");
|
||||
static_assert(!Versioning<BinarySerializable<Foo, 0>>::enabled, "versioning disabled if zero-version specified");
|
||||
static_assert(Versioning<BinarySerializable<Foo, 3>>::applyDefault(0) == 3, "default version returned");
|
||||
static_assert(Versioning<BinarySerializable<Foo, 3>>::applyDefault(2) == 2, "default version overridden");
|
||||
|
|
38
lib/traits.h
38
lib/traits.h
|
@ -8,35 +8,30 @@
|
|||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
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>
|
||||
|
@ -51,14 +46,17 @@ 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<IsSet<Type>>, Traits::Not<IsMultiSet<Type>>>;
|
||||
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
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
#ifndef REFLECTIVE_RAPIDJSON_VERSIONING
|
||||
#define REFLECTIVE_RAPIDJSON_VERSIONING
|
||||
|
||||
#include <c++utilities/misc/traits.h>
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
#ifdef REFLECTIVE_RAPIDJSON_GENERATOR
|
||||
#define REFLECTIVE_RAPIDJSON_CAT_1(a, b) a##b
|
||||
#define REFLECTIVE_RAPIDJSON_CAT_2(a, b) REFLECTIVE_RAPIDJSON_CAT_1(a, b)
|
||||
#define REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version) \
|
||||
static constexpr std::size_t REFLECTIVE_RAPIDJSON_CAT_2(rrjAsOfVersion, __COUNTER__) = version; \
|
||||
public
|
||||
#define REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version) \
|
||||
static constexpr std::size_t REFLECTIVE_RAPIDJSON_CAT_2(rrjUntilVersion, __COUNTER__) = version; \
|
||||
public
|
||||
#else
|
||||
#define REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version) public
|
||||
#define REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version) public
|
||||
#endif
|
||||
|
||||
#ifdef REFLECTIVE_RAPIDJSON_SHORT_MACROS
|
||||
#define as_of_version(version) REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version)
|
||||
#define until_version(version) REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version)
|
||||
#endif
|
||||
|
||||
CPP_UTILITIES_TRAITS_DEFINE_TYPE_CHECK(IsVersioned, T::version);
|
||||
|
||||
template <typename VersionType> struct VersionNotSupported {
|
||||
VersionType presentVersion = 0, maxVersion = 0;
|
||||
const char *record = nullptr;
|
||||
};
|
||||
|
||||
template <typename Type, bool Condition = IsVersioned<Type>::value> struct Versioning {
|
||||
static constexpr auto enabled = false;
|
||||
};
|
||||
|
||||
template <typename Type> struct Versioning<Type, true> {
|
||||
static constexpr auto enabled = Type::version != 0;
|
||||
static constexpr auto serializationDefault = Type::version;
|
||||
static constexpr auto maxSupported = Type::version;
|
||||
static constexpr auto applyDefault(decltype(serializationDefault) version)
|
||||
{
|
||||
return version ? version : serializationDefault;
|
||||
}
|
||||
static constexpr auto isSupported(decltype(maxSupported) version)
|
||||
{
|
||||
return version <= maxSupported;
|
||||
}
|
||||
static constexpr auto assertVersion(decltype(maxSupported) version, const char *record = nullptr)
|
||||
{
|
||||
if (!isSupported(version)) {
|
||||
throw typename Type::VersionNotSupported({ .presentVersion = version, .maxVersion = maxSupported, .record = record });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_TRAITS
|
Loading…
Reference in New Issue