Compare commits
133 Commits
Author | SHA1 | Date |
---|---|---|
Martchus | 743fd60424 | |
Martchus | 5813ee21c5 | |
Martchus | cc1641e0f8 | |
Martchus | 1ea5b1e744 | |
Martchus | 7c8ef68155 | |
Martchus | e3e4596481 | |
Martchus | 091d521152 | |
Martchus | 9a6d550d8f | |
Martchus | e82d834bf2 | |
Martchus | 27b029ba67 | |
Martchus | d50a4c6004 | |
Martchus | 10bb97bbc4 | |
Martchus | b1bd782910 | |
Martchus | 2b0048f144 | |
Martchus | 5c7a6cba8c | |
Martchus | 60d761f7ed | |
Martchus | 9c3bf01c8f | |
Martchus | 1e3417f8d0 | |
Martchus | a6b9d771aa | |
Martchus | 762540f5e5 | |
Martchus | 0633923935 | |
Martchus | 11491b1387 | |
Martchus | df787f3105 | |
Martchus | 2cc044c705 | |
Martchus | 4966625d8b | |
Martchus | efaa8a8441 | |
Martchus | 22611457f9 | |
Martchus | 59ff3c19eb | |
Martchus | f8f551a78a | |
Martchus | 8b66ca3e6b | |
Martchus | 44c6b8c609 | |
Martchus | 13428667f8 | |
Martchus | 852dfb7e3c | |
Martchus | 0a902ac30c | |
Martchus | 5e72012ed5 | |
Martchus | a4dd52acfa | |
Martchus | e3d32ddfa1 | |
Martchus | 8f1909dfdf | |
Martchus | 874c964e0b | |
Martchus | 30735ba187 | |
Martchus | 5110cff5eb | |
Martchus | dc7c74c497 | |
Martchus | 80183f5269 | |
Martchus | 5c49a438ad | |
Martchus | 6252a7335a | |
Martchus | 2b6634d574 | |
Martchus | 0010e32515 | |
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 | |
Martchus | 9b80d662bb | |
Martchus | 669aa16479 | |
Martchus | 2b0acbdd1e | |
Martchus | 0bf19df0d9 | |
Martchus | 04852627b2 | |
Martchus | b058e9e9b9 | |
Martchus | 1f6fade6c2 | |
Martchus | 39dcba215e | |
Martchus | 974c0b0396 | |
Martchus | ef27d71f43 | |
Martchus | 712eb4fb28 | |
Martchus | 7e649fe2bf | |
Martchus | d64174c000 | |
Martchus | f77229471a | |
Martchus | 98b25c813e | |
Martchus | 40b85b411e | |
Martchus | aa92cab4e5 | |
Martchus | 5835cd85a5 | |
Martchus | 933d14ef1d | |
Martchus | 29cd810507 | |
Martchus | 7fcbead8e3 | |
Martchus | 25e7891573 | |
Martchus | da56134456 | |
Martchus | f140c7f436 | |
Martchus | c170993392 | |
Martchus | e93be04e35 | |
Martchus | 6117ef3e1d | |
Martchus | 04dccdbd74 | |
Martchus | ac1fe81497 | |
Martchus | 9dc7bd371c | |
Martchus | 316c1ba838 | |
Martchus | 551ead193e | |
Martchus | 8628427e6d | |
Martchus | 36463cd6dc | |
Martchus | fa6e16b45d | |
Martchus | 7db20f5ad5 | |
Martchus | 03e3a4bc67 |
|
@ -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,20 +1,20 @@
|
|||
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 3)
|
||||
set(META_VERSION_PATCH 16)
|
||||
set(META_APP_VERSION ${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH})
|
||||
|
||||
# set project name for IDEs like Qt Creator
|
||||
project(${META_PROJECT_NAME})
|
||||
set(META_CXX_STANDARD 17)
|
||||
set(META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION ON)
|
||||
|
||||
# ensure testing is enabled at this level (and not only for particular sub directories)
|
||||
enable_testing()
|
||||
|
@ -45,16 +45,13 @@ else()
|
|||
endif()
|
||||
|
||||
# find c++utilities
|
||||
find_package(c++utilities 4.12.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()
|
||||
|
|
230
README.md
230
README.md
|
@ -3,8 +3,13 @@
|
|||
The main goal of this project is to provide a code generator for serializing/deserializing C++ objects to/from JSON
|
||||
using Clang and RapidJSON.
|
||||
|
||||
However, extending the generator to generate code for other applications of reflection or to provide generic
|
||||
reflection would be possible as well.
|
||||
Extending the generator to generate code for other formats or other applications of reflection is possible as well.
|
||||
A serializer/deserializer for a platform independent binary format has already been implemented.
|
||||
|
||||
It would also be possible to extend the library/generator to provide generic reflection (not implemented yet).
|
||||
|
||||
The following documentation focuses on the JSON (de)serializer. However, most of it is also true for the mentioned
|
||||
binary (de)serializer which works quite similar.
|
||||
|
||||
## Open for other reflection approaches
|
||||
The reflection implementation used behind the scenes of this library is exchangeable:
|
||||
|
@ -44,60 +49,82 @@ 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 |
|
||||
| `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`.
|
||||
* The JSON type for smart pointers and `std::optional` depends on the type the pointer/optional refers to.
|
||||
It can also be `null` for null pointers or `std::optional` without value.
|
||||
* If multiple `std::shared_ptr` instances point to the same object this object is serialized multiple times.
|
||||
When deserializing those identical objects, it is currently not possible to share the memory (again). So each
|
||||
`std::shared_ptr` will point to its own copy. Note that this limitation is *not* present when using binary
|
||||
(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.
|
||||
* custom types must provide a default constructor.
|
||||
* constant member variables are skipped.
|
||||
* 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 `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>
|
||||
#include <reflective_rapidjson/json/serializable.h>
|
||||
#include <reflective_rapidjson/json/serializable.h>
|
||||
|
||||
// define structures, eg.
|
||||
struct TestObject : public ReflectiveRapidJSON::JsonSerializable<TestObject> {
|
||||
struct TestObject : public ReflectiveRapidJSON::JsonSerializable<TestObject> {
|
||||
int number;
|
||||
double number2;
|
||||
vector<int> numbers;
|
||||
vector<int> numbers;
|
||||
string text;
|
||||
bool boolean;
|
||||
};
|
||||
struct NestingObject : public ReflectiveRapidJSON::JsonSerializable<NestingObject> {
|
||||
struct NestingObject : public ReflectiveRapidJSON::JsonSerializable<NestingObject> {
|
||||
string name;
|
||||
TestObject testObj;
|
||||
};
|
||||
struct NestingArray : public ReflectiveRapidJSON::JsonSerializable<NestingArray> {
|
||||
struct NestingArray : public ReflectiveRapidJSON::JsonSerializable<NestingArray> {
|
||||
string name;
|
||||
vector<TestObject> testObjects;
|
||||
vector<TestObject> testObjects;
|
||||
};
|
||||
|
||||
// serialize to JSON
|
||||
NestingArray obj{ ... };
|
||||
cout << "JSON: " << obj.toJson().GetString();
|
||||
cout << "JSON: " << obj.toJson().GetString();
|
||||
|
||||
// deserialize from JSON
|
||||
const auto obj = NestingArray::fromJson(...);
|
||||
|
@ -113,11 +140,40 @@ reflective_rapidjson_generator \
|
|||
--output-file "$builddir/reflection/code-defining-structs.h"
|
||||
</pre>
|
||||
|
||||
There are further arguments available, see:
|
||||
<pre>
|
||||
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):
|
||||
|
||||
<pre>
|
||||
#include <reflective_rapidjson/binary/serializable.h>
|
||||
struct TestObject : public ReflectiveRapidJSON::BinarySerializable<TestObject>
|
||||
</pre>
|
||||
|
||||
#### Invoking code generator with CMake macro
|
||||
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)
|
||||
|
||||
|
@ -129,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
|
||||
)
|
||||
|
@ -145,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.
|
||||
|
||||
|
@ -169,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.
|
||||
|
@ -195,34 +253,34 @@ 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> {
|
||||
struct TestObject : public JsonSerializable<TestObject> {
|
||||
BOOST_HANA_DEFINE_STRUCT(TestObject,
|
||||
(int, number),
|
||||
(double, number2),
|
||||
(vector<int>, numbers),
|
||||
(vector<int>, numbers),
|
||||
(string, text),
|
||||
(bool, boolean)
|
||||
);
|
||||
};
|
||||
struct NestingObject : public JsonSerializable<NestingObject> {
|
||||
struct NestingObject : public JsonSerializable<NestingObject> {
|
||||
BOOST_HANA_DEFINE_STRUCT(NestingObject,
|
||||
(string, name),
|
||||
(TestObject, testObj)
|
||||
);
|
||||
};
|
||||
struct NestingArray : public JsonSerializable<NestingArray> {
|
||||
struct NestingArray : public JsonSerializable<NestingArray> {
|
||||
BOOST_HANA_DEFINE_STRUCT(NestingArray,
|
||||
(string, name),
|
||||
(vector<TestObject>, testObjects)
|
||||
(vector<TestObject>, testObjects)
|
||||
);
|
||||
};
|
||||
|
||||
// serialize to JSON
|
||||
NestingArray obj{ ... };
|
||||
cout << "JSON: " << obj.toJson().GetString();
|
||||
cout << "JSON: " << obj.toJson().GetString();
|
||||
|
||||
// deserialize from JSON
|
||||
const auto obj = NestingArray::fromJson(...);
|
||||
|
@ -255,7 +313,7 @@ REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(ThridPartyStruct)
|
|||
|
||||
// (de)serialization
|
||||
ReflectiveRapidJSON::JsonReflector::toJson(...).GetString();
|
||||
ReflectiveRapidJSON::JsonReflector::fromJson<ThridPartyStruct>("...");
|
||||
ReflectiveRapidJSON::JsonReflector::fromJson<ThridPartyStruct>("...");
|
||||
</pre>
|
||||
|
||||
The code generator will emit the code in the same way as if `JsonSerializable` was
|
||||
|
@ -270,7 +328,7 @@ to enable this by adding `friend` methods for the helper functions of Reflective
|
|||
|
||||
To make things easier, there's a macro provided:
|
||||
<pre>
|
||||
struct SomeStruct : public JsonSerializable<SomeStruct> {
|
||||
struct SomeStruct : public JsonSerializable<SomeStruct> {
|
||||
REFLECTIVE_RAPIDJSON_ENABLE_PRIVATE_MEMBERS(SomeStruct);
|
||||
|
||||
public:
|
||||
|
@ -296,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.
|
||||
|
@ -313,7 +419,7 @@ An example for such custom (de)serialization can be found in the file
|
|||
The following diagram gives an overview about the architecture of the code generator and wrapper library
|
||||
around RapidJSON:
|
||||
|
||||
![Architectue overview](/doc/arch.svg)
|
||||
![Architectue overview](./doc/arch.svg)
|
||||
|
||||
* blue: classes from LibTooling/Clang
|
||||
* grey: conceivable extension or use
|
||||
|
@ -325,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)
|
||||
|
@ -344,17 +450,10 @@ 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 or Clang 5.0 as compiler
|
||||
* libstdc++ from GCC 7.2.1
|
||||
* 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 and 1.66.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.
|
||||
|
||||
### How to build
|
||||
#### 1. Install dependencies
|
||||
|
@ -412,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"
|
||||
|
@ -420,8 +519,23 @@ make install DESTDIR="/temporary/install/location"
|
|||
Add eg. `-j$(nproc)` to `make` arguments for using all cores.
|
||||
|
||||
### Packages
|
||||
I currently only provide an
|
||||
[Arch Linux package](https://github.com/Martchus/PKGBUILDs/blob/master/reflective-rapidjson/git/PKGBUILD)
|
||||
for the current Git version. This package shows the required dependencies and commands to build
|
||||
in a plain way. So it might be useful for making Reflective RapidJSON available under other platforms,
|
||||
too.
|
||||
* 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)
|
||||
* there is also a [binary repository](https://martchus.dyn.f3l.de/repo/arch/ownstuff)
|
||||
* Tumbleweed
|
||||
* RPM \*.spec files and binaries are available via openSUSE Build Service
|
||||
* latest releases: [download page](https://software.opensuse.org/download.html?project=home:mkittler&package=reflective-rapidjson-devel),
|
||||
[project page](https://build.opensuse.org/project/show/home:mkittler)
|
||||
* Git master: [download page](https://software.opensuse.org/download.html?project=home:mkittler:vcs&package=reflective-rapidjson-devel),
|
||||
[project page](https://build.opensuse.org/project/show/home:mkittler:vcs)
|
||||
* Windows
|
||||
* for mingw-w64 PKGBUILDs checkout [my GitHub repository](https://github.com/Martchus/PKGBUILDs)
|
||||
|
||||
These packages shows the required dependencies and commands to build in a plain way. So they might be useful for
|
||||
making Reflective RapidJSON available under other platforms, too.
|
||||
|
||||
## Copyright notice and license
|
||||
Copyright © 2017-2024 Marius Kittler
|
||||
|
||||
All code is licensed under [GPL-2-or-later](LICENSE).
|
||||
|
|
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)
|
||||
|
@ -8,50 +8,60 @@ set(LINK_TESTS_AGAINST_APP_TARGET ON)
|
|||
# add project files
|
||||
set(HEADER_FILES
|
||||
codegenerator.h
|
||||
serializationcodegenerator.h
|
||||
jsonserializationcodegenerator.h
|
||||
binaryserializationcodegenerator.h
|
||||
codefactory.h
|
||||
frontendaction.h
|
||||
consumer.h
|
||||
visitor.h
|
||||
clangversionabstraction.h
|
||||
)
|
||||
clangversionabstraction.h)
|
||||
set(SRC_FILES
|
||||
codegenerator.cpp
|
||||
serializationcodegenerator.cpp
|
||||
jsonserializationcodegenerator.cpp
|
||||
binaryserializationcodegenerator.cpp
|
||||
codefactory.cpp
|
||||
frontendaction.cpp
|
||||
consumer.cpp
|
||||
clangversionabstraction.cpp
|
||||
visitor.cpp
|
||||
main.cpp
|
||||
)
|
||||
set(TEST_HEADER_FILES
|
||||
tests/helper.h
|
||||
)
|
||||
set(TEST_SRC_FILES
|
||||
tests/cppunit.cpp
|
||||
)
|
||||
main.cpp)
|
||||
set(TEST_HEADER_FILES tests/helper.h)
|
||||
set(TEST_SRC_FILES tests/binarygenerator.cpp)
|
||||
|
||||
# add JSON-specific test cases
|
||||
if(RapidJSON_FOUND)
|
||||
list(APPEND TEST_HEADER_FILES
|
||||
tests/structs.h
|
||||
tests/morestructs.h
|
||||
)
|
||||
list(APPEND TEST_SRC_FILES
|
||||
tests/jsongenerator.cpp
|
||||
)
|
||||
endif()
|
||||
if (RapidJSON_FOUND)
|
||||
list(APPEND TEST_HEADER_FILES tests/structs.h tests/morestructs.h)
|
||||
list(APPEND TEST_SRC_FILES tests/jsongenerator.cpp)
|
||||
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)
|
||||
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)
|
||||
|
@ -62,38 +72,49 @@ 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(
|
||||
INPUT_FILES
|
||||
tests/structs.h # used by test cases
|
||||
tests/morestructs.h # used by test cases
|
||||
tests/cppunit.cpp # just for testing multiple input files and the "empty file" case
|
||||
visitor.cpp # arbitrarily chosen source file (just for testing the "no relevant structs/classes" case)
|
||||
GENERATORS
|
||||
json
|
||||
binary
|
||||
OUTPUT_LISTS
|
||||
TEST_GENERATED_HEADER_FILES
|
||||
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})
|
||||
endif()
|
||||
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")
|
||||
endif()
|
||||
if (RapidJSON_FOUND)
|
||||
# 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
|
||||
string(APPEND META_CUSTOM_CONFIG "#define REFLECTION_GENERATOR_CLANG_RESOURCE_DIR \"${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR}\"\n")
|
||||
string(APPEND META_CUSTOM_CONFIG
|
||||
"#define REFLECTION_GENERATOR_CLANG_RESOURCE_DIR \"${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR}\"\n")
|
||||
|
||||
# make config header
|
||||
include(ConfigHeader)
|
||||
|
||||
|
|
|
@ -0,0 +1,364 @@
|
|||
#include "./binaryserializationcodegenerator.h"
|
||||
|
||||
#include "../lib/binary/serializable.h"
|
||||
|
||||
#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 CppUtilities;
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
/*!
|
||||
* \brief Initializes the CLI arguments which are specific to the BinarySerializationCodeGenerator.
|
||||
* \todo Find a more general approach to pass CLI arguments from main() to the particular code generators.
|
||||
*/
|
||||
BinarySerializationCodeGenerator::Options::Options()
|
||||
: additionalClassesArg("binary-classes", '\0', "specifies additional classes to consider for binary (de)serialization", { "class-name" })
|
||||
, visibilityArg("binary-visibility", '\0', "specifies the \"visibility attribute\" for generated functions", { "attribute" })
|
||||
{
|
||||
additionalClassesArg.setRequiredValueCount(Argument::varValueCount);
|
||||
additionalClassesArg.setValueCompletionBehavior(ValueCompletionBehavior::None);
|
||||
visibilityArg.setPreDefinedCompletionValues("CPP_UTILITIES_GENERIC_LIB_EXPORT");
|
||||
}
|
||||
|
||||
BinarySerializationCodeGenerator::BinarySerializationCodeGenerator(CodeFactory &factory, const Options &options)
|
||||
: SerializationCodeGenerator(factory)
|
||||
, m_options(options)
|
||||
{
|
||||
m_qualifiedNameOfRecords = BinarySerializable<void>::qualifiedName;
|
||||
m_qualifiedNameOfAdaptionRecords = AdaptedBinarySerializable<void>::qualifiedName;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Checks whether \a possiblyRelevantClass is actually relevant.
|
||||
*/
|
||||
void BinarySerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
|
||||
{
|
||||
SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
|
||||
if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
|
||||
return;
|
||||
}
|
||||
|
||||
// consider all classes specified via "--additional-classes" argument relevant
|
||||
if (!m_options.additionalClassesArg.isPresent()) {
|
||||
return;
|
||||
}
|
||||
for (const char *const className : m_options.additionalClassesArg.values()) {
|
||||
if (className == possiblyRelevantClass.qualifiedName) {
|
||||
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \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(std::ostream &os) const
|
||||
{
|
||||
// initialize source manager to make use of isOnlyIncluded() for skipping records which are only included
|
||||
lazyInitializeSourceManager();
|
||||
|
||||
// find relevant classes
|
||||
const auto relevantClasses = findRelevantClasses();
|
||||
if (relevantClasses.empty()) {
|
||||
return; // nothing to generate
|
||||
}
|
||||
|
||||
// put everything into namespace ReflectiveRapidJSON::BinaryReflector
|
||||
os << "namespace ReflectiveRapidJSON {\n"
|
||||
"namespace BinaryReflector {\n\n";
|
||||
|
||||
// determine visibility attribute
|
||||
const char *visibility = m_options.visibilityArg.firstValue();
|
||||
if (!visibility) {
|
||||
visibility = "";
|
||||
}
|
||||
|
||||
// add push and pull functions for each class, for an example of the resulting
|
||||
// output, see ../lib/tests/binaryserializable.cpp
|
||||
for (const RelevantClass &relevantClass : relevantClasses) {
|
||||
// determine whether private members should be pushed/pulled as well: check whether friend declarations for push/pull present
|
||||
// note: the friend declarations we are looking for are expanded from the REFLECTIVE_RAPIDJSON_ENABLE_PRIVATE_MEMBERS macro
|
||||
bool writePrivateMembers = false, readPrivateMembers = false;
|
||||
for (const clang::FriendDecl *const friendDecl : relevantClass.record->friends()) {
|
||||
// get the actual declaration which must be a function
|
||||
const clang::NamedDecl *const actualFriendDecl = friendDecl->getFriendDecl();
|
||||
if (!actualFriendDecl || actualFriendDecl->getKind() != clang::Decl::Kind::Function) {
|
||||
continue;
|
||||
}
|
||||
// check whether the friend function matches the push/pull helper function
|
||||
const string friendName(actualFriendDecl->getQualifiedNameAsString());
|
||||
if (friendName == "ReflectiveRapidJSON::BinaryReflector::writeCustomType") {
|
||||
writePrivateMembers = true;
|
||||
}
|
||||
if (friendName == "ReflectiveRapidJSON::BinaryReflector::readCustomType") {
|
||||
readPrivateMembers = true;
|
||||
}
|
||||
if (writePrivateMembers && readPrivateMembers) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// find relevant base classes
|
||||
const vector<const RelevantClass *> relevantBases = findRelevantBaseClasses(relevantClass, relevantClasses);
|
||||
|
||||
// print comment
|
||||
os << "// define code for (de)serializing " << relevantClass.qualifiedName << " objects\n";
|
||||
|
||||
// print writeCustomType method
|
||||
os << "template <> " << visibility << " void writeCustomType<::" << relevantClass.qualifiedName
|
||||
<< ">(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), version);\n";
|
||||
}
|
||||
if (!relevantClass.relevantBase.empty()) {
|
||||
os << " // write version\n"
|
||||
" using V = Versioning<"
|
||||
<< relevantClass.relevantBase
|
||||
<< ">;\n"
|
||||
" if constexpr (V::enabled) {\n"
|
||||
" serializer.writeVariableLengthUIntBE(V::applyDefault(version));\n"
|
||||
" }\n";
|
||||
}
|
||||
os << " // write members\n";
|
||||
auto 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";
|
||||
|
||||
// skip printing the readCustomType method for classes without default constructor because deserializing those is currently not supported
|
||||
if (!relevantClass.record->hasDefaultConstructor()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// print readCustomType method
|
||||
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), 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 (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() << ", 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";
|
||||
}
|
||||
|
||||
// close namespace ReflectiveRapidJSON::BinaryReflector
|
||||
os << "} // namespace BinaryReflector\n"
|
||||
"} // namespace ReflectiveRapidJSON\n";
|
||||
}
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
|
@ -0,0 +1,43 @@
|
|||
#ifndef REFLECTIVE_RAPIDJSON_CODE_BINARY_SERIALIZATION_GENERATOR_H
|
||||
#define REFLECTIVE_RAPIDJSON_CODE_BINARY_SERIALIZATION_GENERATOR_H
|
||||
|
||||
#include "./serializationcodegenerator.h"
|
||||
|
||||
#include <c++utilities/application/argumentparser.h>
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
/*!
|
||||
* \brief The BinarySerializationCodeGenerator class generates code for JSON (de)serialization
|
||||
* of objects inheriting from an instantiation of JsonSerializable.
|
||||
*/
|
||||
class BinarySerializationCodeGenerator : public SerializationCodeGenerator {
|
||||
public:
|
||||
struct Options {
|
||||
Options();
|
||||
Options(const Options &other) = delete;
|
||||
void appendTo(CppUtilities::Argument *arg);
|
||||
|
||||
CppUtilities::ConfigValueArgument additionalClassesArg;
|
||||
CppUtilities::ConfigValueArgument visibilityArg;
|
||||
};
|
||||
|
||||
BinarySerializationCodeGenerator(CodeFactory &factory, const Options &options);
|
||||
|
||||
void generate(std::ostream &os) const override;
|
||||
|
||||
protected:
|
||||
void computeRelevantClass(RelevantClass &possiblyRelevantClass) const override;
|
||||
|
||||
const Options &m_options;
|
||||
};
|
||||
|
||||
inline void BinarySerializationCodeGenerator::Options::appendTo(CppUtilities::Argument *arg)
|
||||
{
|
||||
arg->addSubArgument(&additionalClassesArg);
|
||||
arg->addSubArgument(&visibilityArg);
|
||||
}
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_CODE_BINARY_SERIALIZATION_GENERATOR_H
|
|
@ -2,17 +2,6 @@
|
|||
|
||||
#include "./clangversionabstraction.h"
|
||||
|
||||
#include <clang/Lex/Preprocessor.h>
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
clang::FileID createFileIDForMemBuffer(clang::Preprocessor &pp, llvm::MemoryBuffer *buffer, clang::SourceLocation location)
|
||||
{
|
||||
#if CLANG_VERSION_MAJOR != 3 || CLANG_VERSION_MINOR > 4
|
||||
return pp.getSourceManager().createFileID(maybe_unique(buffer), clang::SrcMgr::C_User, 0, 0, location);
|
||||
#else
|
||||
return pp.getSourceManager().createFileIDForMemBuffer(buffer, clang::SrcMgr::C_User, 0, 0, location);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
|
|
@ -8,14 +8,8 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
namespace clang {
|
||||
class Preprocessor;
|
||||
}
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
clang::FileID createFileIDForMemBuffer(clang::Preprocessor &pp, llvm::MemoryBuffer *buffer, clang::SourceLocation location);
|
||||
|
||||
/*!
|
||||
* \brief The MaybeUnique class represents either a std::unique_ptr or a raw pointer.
|
||||
* \remarks This is used to support Clang < 3.6 which has not been using unique_ptr in many places yet.
|
||||
|
@ -40,7 +34,7 @@ template <typename T> MaybeUnique<T> maybe_unique(std::unique_ptr<T> val)
|
|||
return { val.release() };
|
||||
}
|
||||
|
||||
/*!
|
||||
/*!
|
||||
* \def REFLECTIVE_RAPIDJSON_MAYBE_UNIQUE
|
||||
* \brief The REFLECTIVE_RAPIDJSON_MAYBE_UNIQUE macro either expands to a std::unique_ptr or a raw pointer depending on the Clang version.
|
||||
*/
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
@ -18,137 +18,42 @@ namespace ReflectiveRapidJSON {
|
|||
* \todo Find a more general approach to pass CLI arguments from main() to the particular code generators.
|
||||
*/
|
||||
JsonSerializationCodeGenerator::Options::Options()
|
||||
: additionalClassesArg("json-classes", '\0', "specifies additional classes to consider for JSON serialization", { "class-name" })
|
||||
: additionalClassesArg("json-classes", '\0', "specifies additional classes to consider for JSON (de)serialization", { "class-name" })
|
||||
, visibilityArg("json-visibility", '\0', "specifies the \"visibility attribute\" for generated functions", { "attribute" })
|
||||
{
|
||||
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)
|
||||
: SerializationCodeGenerator(factory)
|
||||
, m_options(options)
|
||||
{
|
||||
m_qualifiedNameOfRecords = JsonSerializable<void>::qualifiedName;
|
||||
m_qualifiedNameOfAdaptionRecords = AdaptedJsonSerializable<void>::qualifiedName;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Adds all class declarations (to the internal member variable m_records).
|
||||
* \remarks "AdaptedJsonSerializable" specializations are directly filtered and added to m_adaptionRecords (instead of m_records).
|
||||
* \brief Checks whether \a possiblyRelevantClass is actually relevant.
|
||||
*/
|
||||
void JsonSerializationCodeGenerator::addDeclaration(clang::Decl *decl)
|
||||
void JsonSerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
|
||||
{
|
||||
switch (decl->getKind()) {
|
||||
case clang::Decl::Kind::CXXRecord:
|
||||
case clang::Decl::Kind::ClassTemplateSpecialization: {
|
||||
auto *const record = static_cast<clang::CXXRecordDecl *>(decl);
|
||||
// skip forward declarations
|
||||
if (!record->hasDefinition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for template specializations to adapt a 3rd party class/struct
|
||||
if (decl->getKind() == clang::Decl::Kind::ClassTemplateSpecialization) {
|
||||
auto *const templateSpecializationRecord = static_cast<clang::ClassTemplateSpecializationDecl *>(decl);
|
||||
// check whether the name of the template specialization matches
|
||||
if (templateSpecializationRecord->getQualifiedNameAsString() == AdaptedJsonSerializable<void>::qualifiedName) {
|
||||
// get the template argument of the template specialization (exactly one argument expected)
|
||||
const auto &templateArgs = templateSpecializationRecord->getTemplateArgs();
|
||||
if (templateArgs.size() != 1 || templateArgs.get(0).getKind() != clang::TemplateArgument::Type) {
|
||||
return; // FIXME: use Clang diagnostics to print warning
|
||||
}
|
||||
// get the type the template argument refers to (that's the type of the 3rd party class/struct to adapt)
|
||||
auto *const templateRecord = templateArgs.get(0).getAsType()->getAsCXXRecordDecl();
|
||||
if (!templateRecord) {
|
||||
return; // FIXME: use Clang diagnostics to print warning
|
||||
}
|
||||
// save the relevant information for the code generation
|
||||
m_adaptionRecords.emplace_back(templateRecord->getQualifiedNameAsString(), templateSpecializationRecord);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// add any other records
|
||||
m_records.emplace_back(record);
|
||||
} break;
|
||||
case clang::Decl::Kind::Enum:
|
||||
// TODO: add enums
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the qualified name of the specified \a record if it is considered relevant.
|
||||
*/
|
||||
string JsonSerializationCodeGenerator::qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const
|
||||
{
|
||||
// consider all classes for which a specialization of the "AdaptedJsonSerializable" struct is available
|
||||
const string qualifiedName(record->getQualifiedNameAsString());
|
||||
for (const auto &adaptionRecord : m_adaptionRecords) {
|
||||
// skip all adaption records which are only included
|
||||
if (isOnlyIncluded(adaptionRecord.record)) {
|
||||
continue;
|
||||
}
|
||||
if (adaptionRecord.qualifiedName == qualifiedName) {
|
||||
return qualifiedName;
|
||||
}
|
||||
}
|
||||
|
||||
// skip all classes which are only included
|
||||
if (isOnlyIncluded(record)) {
|
||||
return string();
|
||||
}
|
||||
|
||||
// consider all classes inheriting from an instantiation of "JsonSerializable" relevant
|
||||
if (inheritsFromInstantiationOf(record, JsonSerializable<void>::qualifiedName)) {
|
||||
return qualifiedName;
|
||||
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 Searches the records added via addDeclaration() and returns the relevant ones.
|
||||
* \sa Whether a record is relevant is determined using the qualifiedNameIfRelevant() method.
|
||||
*/
|
||||
std::vector<JsonSerializationCodeGenerator::RelevantClass> JsonSerializationCodeGenerator::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);
|
||||
}
|
||||
}
|
||||
return relevantClasses;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the relevant base classes of the specified \a relevantClass. All base classes in \a relevantBases are considered relevant.
|
||||
*/
|
||||
std::vector<const JsonSerializationCodeGenerator::RelevantClass *> JsonSerializationCodeGenerator::findRelevantBaseClasses(
|
||||
const RelevantClass &relevantClass, const std::vector<RelevantClass> &relevantBases)
|
||||
{
|
||||
vector<const RelevantClass *> relevantBaseClasses;
|
||||
for (const RelevantClass &otherClass : relevantBases) {
|
||||
if (relevantClass.record != otherClass.record && relevantClass.record->isDerivedFrom(otherClass.record)) {
|
||||
relevantBaseClasses.push_back(&otherClass);
|
||||
}
|
||||
}
|
||||
return relevantBaseClasses;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Prints an LLVM string reference without instantiating a std::string first.
|
||||
*/
|
||||
ostream &operator<<(ostream &os, llvm::StringRef str)
|
||||
{
|
||||
return os.write(str.data(), static_cast<streamsize>(str.size()));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -214,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
|
||||
|
@ -236,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 = \""
|
||||
|
@ -244,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())) {
|
||||
|
@ -251,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"
|
||||
|
|
|
@ -1,70 +1,43 @@
|
|||
#ifndef REFLECTIVE_RAPIDJSON_CODE_JSON_SERIALIZATION_GENERATOR_H
|
||||
#define REFLECTIVE_RAPIDJSON_CODE_JSON_SERIALIZATION_GENERATOR_H
|
||||
|
||||
#include "./codegenerator.h"
|
||||
#include "./serializationcodegenerator.h"
|
||||
|
||||
#include <c++utilities/application/argumentparser.h>
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
/*!
|
||||
* \brief The JSONSerializationCodeGenerator class generates code for JSON (de)serialization
|
||||
* \brief The JsonSerializationCodeGenerator class generates code for JSON (de)serialization
|
||||
* of objects inheriting from an instantiation of JsonSerializable.
|
||||
*/
|
||||
class JsonSerializationCodeGenerator : public CodeGenerator {
|
||||
class JsonSerializationCodeGenerator : public SerializationCodeGenerator {
|
||||
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;
|
||||
};
|
||||
|
||||
private:
|
||||
struct RelevantClass {
|
||||
explicit RelevantClass(std::string &&qualifiedName, clang::CXXRecordDecl *record);
|
||||
|
||||
std::string qualifiedName;
|
||||
clang::CXXRecordDecl *record;
|
||||
};
|
||||
|
||||
public:
|
||||
JsonSerializationCodeGenerator(CodeFactory &factory, const Options &options);
|
||||
|
||||
void addDeclaration(clang::Decl *decl) override;
|
||||
void generate(std::ostream &os) const override;
|
||||
|
||||
private:
|
||||
std::string qualifiedNameIfRelevant(clang::CXXRecordDecl *record) const;
|
||||
std::vector<RelevantClass> findRelevantClasses() const;
|
||||
static std::vector<const RelevantClass *> findRelevantBaseClasses(
|
||||
const RelevantClass &relevantClass, const std::vector<RelevantClass> &relevantBases);
|
||||
protected:
|
||||
void computeRelevantClass(RelevantClass &possiblyRelevantClass) const override;
|
||||
|
||||
std::vector<clang::CXXRecordDecl *> m_records;
|
||||
std::vector<RelevantClass> m_adaptionRecords;
|
||||
const Options &m_options;
|
||||
};
|
||||
|
||||
inline JsonSerializationCodeGenerator::JsonSerializationCodeGenerator(CodeFactory &factory, const Options &options)
|
||||
: CodeGenerator(factory)
|
||||
, m_options(options)
|
||||
{
|
||||
}
|
||||
|
||||
inline void JsonSerializationCodeGenerator::Options::appendTo(ApplicationUtilities::Argument *arg)
|
||||
inline void JsonSerializationCodeGenerator::Options::appendTo(CppUtilities::Argument *arg)
|
||||
{
|
||||
arg->addSubArgument(&additionalClassesArg);
|
||||
arg->addSubArgument(&visibilityArg);
|
||||
}
|
||||
|
||||
inline JsonSerializationCodeGenerator::RelevantClass::RelevantClass(std::string &&qualifiedName, clang::CXXRecordDecl *record)
|
||||
: qualifiedName(qualifiedName)
|
||||
, record(record)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_CODE_JSON_SERIALIZATION_GENERATOR_H
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "./binaryserializationcodegenerator.h"
|
||||
#include "./codefactory.h"
|
||||
#include "./jsonserializationcodegenerator.h"
|
||||
|
||||
|
@ -5,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>
|
||||
|
@ -17,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[])
|
||||
|
@ -36,22 +33,25 @@ 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;
|
||||
binaryOptions.appendTo(&generateArg);
|
||||
parser.setMainArguments({ &generateArg, &noColorArg, &helpArg });
|
||||
|
||||
// parse arguments
|
||||
parser.parseArgsOrExit(argc, argv);
|
||||
parser.parseArgs(argc, argv);
|
||||
if (helpArg.isPresent() || !generateArg.isPresent()) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -69,28 +69,36 @@ 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()) {
|
||||
// define mapping of generator names to generator constructors (add new generators here!)
|
||||
// clang-format off
|
||||
const std::unordered_map<std::string, std::function<void()>> generatorsByName{
|
||||
{ "json", factory.bindGenerator<JsonSerializationCodeGenerator, const JsonSerializationCodeGenerator::Options &>(jsonOptions) }
|
||||
{ "json", factory.bindGenerator<JsonSerializationCodeGenerator, const JsonSerializationCodeGenerator::Options &>(jsonOptions) },
|
||||
{ "binary", factory.bindGenerator<BinarySerializationCodeGenerator, const BinarySerializationCodeGenerator::Options &>(binaryOptions) },
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
@ -115,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.";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
#include "./serializationcodegenerator.h"
|
||||
|
||||
#include <c++utilities/application/global.h>
|
||||
|
||||
#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;
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
/*!
|
||||
* \brief Prints an LLVM string reference without instantiating a std::string first.
|
||||
*/
|
||||
ostream &operator<<(ostream &os, llvm::StringRef str)
|
||||
{
|
||||
return os.write(str.data(), static_cast<streamsize>(str.size()));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Adds all class declarations (to the internal member variable m_records).
|
||||
* \remarks "AdaptedXXXSerializable" specializations are directly filtered and added to m_adaptionRecords (instead of m_records).
|
||||
*/
|
||||
void SerializationCodeGenerator::addDeclaration(clang::Decl *decl)
|
||||
{
|
||||
switch (decl->getKind()) {
|
||||
case clang::Decl::Kind::CXXRecord:
|
||||
case clang::Decl::Kind::ClassTemplateSpecialization: {
|
||||
auto *const record = static_cast<clang::CXXRecordDecl *>(decl);
|
||||
// skip forward declarations
|
||||
if (!record->hasDefinition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for template specializations to adapt a 3rd party class/struct
|
||||
if (m_qualifiedNameOfAdaptionRecords && decl->getKind() == clang::Decl::Kind::ClassTemplateSpecialization) {
|
||||
auto *const templateSpecializationRecord = static_cast<clang::ClassTemplateSpecializationDecl *>(decl);
|
||||
// check whether the name of the template specialization matches
|
||||
if (templateSpecializationRecord->getQualifiedNameAsString() == m_qualifiedNameOfAdaptionRecords) {
|
||||
// get the template argument of the template specialization (exactly one argument expected)
|
||||
const auto &templateArgs = templateSpecializationRecord->getTemplateArgs();
|
||||
if (templateArgs.size() != 1 || templateArgs.get(0).getKind() != clang::TemplateArgument::Type) {
|
||||
return; // FIXME: use Clang diagnostics to print warning
|
||||
}
|
||||
// get the type the template argument refers to (that's the type of the 3rd party class/struct to adapt)
|
||||
auto *const templateRecord = templateArgs.get(0).getAsType()->getAsCXXRecordDecl();
|
||||
if (!templateRecord) {
|
||||
return; // FIXME: use Clang diagnostics to print warning
|
||||
}
|
||||
// save the relevant information for the code generation
|
||||
m_adaptionRecords.emplace_back(templateRecord->getQualifiedNameAsString(), templateSpecializationRecord);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// add any other records
|
||||
m_records.emplace_back(record);
|
||||
} break;
|
||||
case clang::Decl::Kind::Enum:
|
||||
// TODO: add enums
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void SerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
|
||||
{
|
||||
// skip all classes which are only forward-declared
|
||||
if (!possiblyRelevantClass.record->isCompleteDefinition()) {
|
||||
possiblyRelevantClass.isRelevant = IsRelevant::No;
|
||||
return;
|
||||
}
|
||||
|
||||
// consider all classes for which a specialization of the "AdaptedJsonSerializable" struct is available
|
||||
for (const auto &adaptionRecord : m_adaptionRecords) {
|
||||
// skip all adaption records which are only included
|
||||
if (isOnlyIncluded(adaptionRecord.record)) {
|
||||
continue;
|
||||
}
|
||||
if (adaptionRecord.qualifiedName == possiblyRelevantClass.qualifiedName) {
|
||||
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// skip all classes which are only included
|
||||
if (isOnlyIncluded(possiblyRelevantClass.record)) {
|
||||
possiblyRelevantClass.isRelevant = IsRelevant::No;
|
||||
return;
|
||||
}
|
||||
|
||||
// consider all classes inheriting from an instantiation of "JsonSerializable" relevant
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SerializationCodeGenerator::RelevantClass> SerializationCodeGenerator::findRelevantClasses() const
|
||||
{
|
||||
std::vector<RelevantClass> relevantClasses;
|
||||
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;
|
||||
}
|
||||
|
||||
std::vector<const SerializationCodeGenerator::RelevantClass *> SerializationCodeGenerator::findRelevantBaseClasses(
|
||||
const SerializationCodeGenerator::RelevantClass &relevantClass, const std::vector<SerializationCodeGenerator::RelevantClass> &relevantBases)
|
||||
{
|
||||
vector<const RelevantClass *> relevantBaseClasses;
|
||||
for (const RelevantClass &otherClass : relevantBases) {
|
||||
if (relevantClass.record != otherClass.record && relevantClass.record->isDerivedFrom(otherClass.record)) {
|
||||
relevantBaseClasses.push_back(&otherClass);
|
||||
}
|
||||
}
|
||||
return relevantBaseClasses;
|
||||
}
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
|
@ -0,0 +1,64 @@
|
|||
#ifndef REFLECTIVE_RAPIDJSON_SERIALIZATION_CODE_GENERATOR_H
|
||||
#define REFLECTIVE_RAPIDJSON_SERIALIZATION_CODE_GENERATOR_H
|
||||
|
||||
#include "./codegenerator.h"
|
||||
|
||||
#include <llvm/ADT/StringRef.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, llvm::StringRef str);
|
||||
|
||||
/*!
|
||||
* \brief The SerializationCodeGenerator class is the common base for (de)serialization
|
||||
* related code generation.
|
||||
*/
|
||||
class SerializationCodeGenerator : public CodeGenerator {
|
||||
public:
|
||||
enum class IsRelevant { Yes, No, Maybe };
|
||||
struct RelevantClass {
|
||||
explicit RelevantClass(std::string &&qualifiedName, clang::CXXRecordDecl *record);
|
||||
|
||||
std::string qualifiedName;
|
||||
std::string relevantBase;
|
||||
clang::CXXRecordDecl *record = nullptr;
|
||||
IsRelevant isRelevant = IsRelevant::Maybe;
|
||||
};
|
||||
|
||||
SerializationCodeGenerator(CodeFactory &factory);
|
||||
|
||||
void addDeclaration(clang::Decl *decl) override;
|
||||
|
||||
protected:
|
||||
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);
|
||||
|
||||
protected:
|
||||
const char *m_qualifiedNameOfRecords;
|
||||
const char *m_qualifiedNameOfAdaptionRecords;
|
||||
|
||||
private:
|
||||
std::vector<clang::CXXRecordDecl *> m_records;
|
||||
std::vector<RelevantClass> m_adaptionRecords;
|
||||
};
|
||||
|
||||
inline SerializationCodeGenerator::RelevantClass::RelevantClass(std::string &&qualifiedName, clang::CXXRecordDecl *record)
|
||||
: qualifiedName(qualifiedName)
|
||||
, record(record)
|
||||
{
|
||||
}
|
||||
|
||||
inline SerializationCodeGenerator::SerializationCodeGenerator(CodeFactory &factory)
|
||||
: CodeGenerator(factory)
|
||||
, m_qualifiedNameOfRecords(nullptr)
|
||||
, m_qualifiedNameOfAdaptionRecords(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_SERIALIZATION_CODE_GENERATOR_H
|
|
@ -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";
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
#include "./helper.h"
|
||||
#include "./morestructs.h"
|
||||
#include "./structs.h"
|
||||
|
||||
#include "../codefactory.h"
|
||||
#include "../jsonserializationcodegenerator.h"
|
||||
|
||||
#include "resources/config.h"
|
||||
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
#include <c++utilities/io/misc.h>
|
||||
#include <c++utilities/tests/testutils.h>
|
||||
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
using namespace CPPUNIT_NS;
|
||||
using namespace CppUtilities;
|
||||
using namespace CppUtilities::Literals;
|
||||
|
||||
/*!
|
||||
* \brief The BinaryGeneratorTests class tests the binary generator.
|
||||
*/
|
||||
class BinaryGeneratorTests : public TestFixture {
|
||||
CPPUNIT_TEST_SUITE(BinaryGeneratorTests);
|
||||
CPPUNIT_TEST(testSerializationAndDeserialization);
|
||||
CPPUNIT_TEST(testPointerHandling);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
BinaryGeneratorTests();
|
||||
void testSerializationAndDeserialization();
|
||||
void testPointerHandling();
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(BinaryGeneratorTests);
|
||||
|
||||
BinaryGeneratorTests::BinaryGeneratorTests()
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests serializing some objects and deserialize them back.
|
||||
*/
|
||||
void BinaryGeneratorTests::testSerializationAndDeserialization()
|
||||
{
|
||||
DerivedTestStruct obj;
|
||||
obj.someInt = 25;
|
||||
obj.someSize = 27;
|
||||
obj.someString = "foo";
|
||||
obj.someBool = true;
|
||||
|
||||
stringstream stream(ios_base::in | ios_base::out | ios_base::binary);
|
||||
stream.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
|
||||
static_cast<BinarySerializable<DerivedTestStruct> &>(obj).toBinary(stream);
|
||||
|
||||
const auto deserializedObj(BinarySerializable<DerivedTestStruct>::fromBinary(stream));
|
||||
CPPUNIT_ASSERT_EQUAL(obj.someInt, deserializedObj.someInt);
|
||||
CPPUNIT_ASSERT_EQUAL(obj.someSize, deserializedObj.someSize);
|
||||
CPPUNIT_ASSERT_EQUAL(obj.someString, deserializedObj.someString);
|
||||
CPPUNIT_ASSERT_EQUAL(obj.someBool, deserializedObj.someBool);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests handling of std::unique_ptr and std::shared_ptr.
|
||||
*
|
||||
* In particular, the same object referred by 2 related std::shared_ptr instances
|
||||
*
|
||||
* - should be serialized only once
|
||||
* - and result in 2 related std::shared_ptr instances again after deserialization.
|
||||
*/
|
||||
void BinaryGeneratorTests::testPointerHandling()
|
||||
{
|
||||
PointerStruct ps;
|
||||
ps.s1 = make_shared<PointerTarget>(0xF1F2F3F3);
|
||||
ps.s2 = ps.s1;
|
||||
ps.s3 = make_shared<PointerTarget>(0xBBBBBBBB);
|
||||
ps.u1 = make_unique<PointerTarget>(0xF1F2F3F4);
|
||||
ps.u2 = make_unique<PointerTarget>(0xDDDDDDDD);
|
||||
ps.u3 = make_unique<PointerTarget>(0xEEEEEEEE);
|
||||
|
||||
// check whether shared pointer are "wired" as expected
|
||||
++ps.s1->n; // should affect s2 but not s3
|
||||
CPPUNIT_ASSERT_EQUAL(asHexNumber<uint32_t>(0xF1F2F3F4), asHexNumber<uint32_t>(ps.s1->n));
|
||||
CPPUNIT_ASSERT_EQUAL(asHexNumber<uint32_t>(0xF1F2F3F4), asHexNumber<uint32_t>(ps.s2->n));
|
||||
CPPUNIT_ASSERT_EQUAL(asHexNumber<uint32_t>(0xBBBBBBBB), asHexNumber<uint32_t>(ps.s3->n));
|
||||
|
||||
// serialize and deserialize
|
||||
stringstream stream(ios_base::in | ios_base::out | ios_base::binary);
|
||||
stream.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
ps.toBinary(stream);
|
||||
const auto deserializedPs(PointerStruct::fromBinary(stream));
|
||||
|
||||
// check shared pointer
|
||||
CPPUNIT_ASSERT_EQUAL(asHexNumber<uint32_t>(0xF1F2F3F4), asHexNumber<uint32_t>(deserializedPs.s1->n));
|
||||
CPPUNIT_ASSERT_EQUAL(asHexNumber<uint32_t>(0xF1F2F3F4), asHexNumber<uint32_t>(deserializedPs.s2->n));
|
||||
CPPUNIT_ASSERT_EQUAL(asHexNumber<uint32_t>(0xBBBBBBBB), asHexNumber<uint32_t>(deserializedPs.s3->n));
|
||||
++deserializedPs.s1->n; // should affect s2 but not s3
|
||||
CPPUNIT_ASSERT_EQUAL(asHexNumber<uint32_t>(0xF1F2F3F5), asHexNumber<uint32_t>(deserializedPs.s1->n));
|
||||
CPPUNIT_ASSERT_EQUAL(asHexNumber<uint32_t>(0xF1F2F3F5), asHexNumber<uint32_t>(deserializedPs.s2->n));
|
||||
CPPUNIT_ASSERT_EQUAL(asHexNumber<uint32_t>(0xBBBBBBBB), asHexNumber<uint32_t>(deserializedPs.s3->n));
|
||||
|
||||
// check unique pointer
|
||||
CPPUNIT_ASSERT_EQUAL(asHexNumber<uint32_t>(0xF1F2F3F4), asHexNumber<uint32_t>(deserializedPs.u1->n));
|
||||
CPPUNIT_ASSERT_EQUAL(asHexNumber<uint32_t>(0xDDDDDDDD), asHexNumber<uint32_t>(deserializedPs.u2->n));
|
||||
CPPUNIT_ASSERT_EQUAL(asHexNumber<uint32_t>(0xEEEEEEEE), asHexNumber<uint32_t>(deserializedPs.u3->n));
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
#include <c++utilities/tests/cppunit.h>
|
|
@ -6,14 +6,16 @@
|
|||
#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.
|
||||
*/
|
||||
template <typename Iteratable, Traits::EnableIf<Traits::IsIteratable<Iteratable>, Traits::Not<Traits::IsString<Iteratable>>>...>
|
||||
template <typename Iteratable, Traits::EnableIf<Traits::IsIteratable<Iteratable>, Traits::Not<Traits::IsString<Iteratable>>> * = nullptr>
|
||||
inline void assertEqualityLinewise(const Iteratable &iteratable1, const Iteratable &iteratable2)
|
||||
{
|
||||
std::vector<std::string> differentLines;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "resources/config.h"
|
||||
|
||||
#include <c++utilities/conversion/stringbuilder.h>
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
#include <c++utilities/io/misc.h>
|
||||
#include <c++utilities/tests/testutils.h>
|
||||
|
@ -16,13 +17,11 @@
|
|||
#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 OverallTests class tests the overall functionality of the code generator (CLI and generator itself).
|
||||
* \brief The JsonGeneratorTests class tests the overall functionality of the code generator (CLI and generator itself) and JSON specific parts.
|
||||
*/
|
||||
class JsonGeneratorTests : public TestFixture {
|
||||
CPPUNIT_TEST_SUITE(JsonGeneratorTests);
|
||||
|
@ -65,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;
|
||||
|
@ -81,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.
|
||||
*/
|
||||
|
@ -91,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
|
||||
|
@ -158,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()
|
||||
{
|
||||
|
@ -187,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()
|
||||
{
|
||||
|
@ -303,4 +309,4 @@ void JsonGeneratorTests::testHandlingConstMembers()
|
|||
|
||||
// this file should also be generated via add_reflection_generator_invocation() and hence includeable
|
||||
// it is included to test the "empty" case when a unit doesn't contain relevant classes
|
||||
#include "reflection/cppunit.h"
|
||||
#include "reflection/visitor.h"
|
||||
|
|
|
@ -1,7 +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;
|
||||
|
@ -15,7 +19,7 @@ using namespace ReflectiveRapidJSON;
|
|||
*
|
||||
* \remarks This is important to prevent violating the one definition rule.
|
||||
*/
|
||||
struct IncludedStruct : public JsonSerializable<IncludedStruct> {
|
||||
struct IncludedStruct : public JsonSerializable<IncludedStruct>, public BinarySerializable<IncludedStruct> {
|
||||
int someInt = 0;
|
||||
};
|
||||
|
||||
|
@ -23,9 +27,72 @@ struct IncludedStruct : public JsonSerializable<IncludedStruct> {
|
|||
* \brief The ConstStruct struct is used to test handling of const members.
|
||||
* \remarks Those members should be ignored when deserializing.
|
||||
*/
|
||||
struct ConstStruct : public JsonSerializable<ConstStruct> {
|
||||
struct ConstStruct : public JsonSerializable<ConstStruct>, public BinarySerializable<IncludedStruct> {
|
||||
int modifiableInt = 23;
|
||||
const int constInt = 42;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The PointerTarget struct is used to test the behavior of the binary (de)serialization with smart pointer.
|
||||
*/
|
||||
struct PointerTarget : public BinarySerializable<PointerTarget> {
|
||||
PointerTarget()
|
||||
: n(0xAAAAAAAA)
|
||||
, dummy1(0x1111111111111111)
|
||||
, dummy2(0x1111111111111111)
|
||||
, dummy3(0x1111111111111111)
|
||||
{
|
||||
}
|
||||
|
||||
PointerTarget(uint32_t n)
|
||||
: n(n)
|
||||
, dummy1(0x1111111111111111)
|
||||
, dummy2(0x1111111111111111)
|
||||
, dummy3(0x1111111111111111)
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t n;
|
||||
uint64_t dummy1;
|
||||
uint64_t dummy2;
|
||||
uint64_t dummy3;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The PointerStruct struct is used to test the behavior of the binary (de)serialization with smart pointer.
|
||||
*/
|
||||
struct PointerStruct : public BinarySerializable<PointerStruct> {
|
||||
std::shared_ptr<PointerTarget> s1;
|
||||
std::unique_ptr<PointerTarget> u2;
|
||||
std::unique_ptr<PointerTarget> u3;
|
||||
std::shared_ptr<PointerTarget> s2;
|
||||
std::unique_ptr<PointerTarget> u1;
|
||||
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
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
// contained structs as well (to prevent violating the one definition rule)
|
||||
#include "./morestructs.h"
|
||||
|
||||
#include "../../lib/binary/reflector-chronoutilities.h"
|
||||
#include "../../lib/binary/serializable.h"
|
||||
#include "../../lib/json/reflector-chronoutilities.h"
|
||||
#include "../../lib/json/serializable.h"
|
||||
|
||||
|
@ -23,7 +25,7 @@ using namespace ReflectiveRapidJSON;
|
|||
* \brief The TestStruct struct inherits from JsonSerializable and should hence have functional fromJson()
|
||||
* and toJson() methods. This is asserted in JsonGeneratorTests::testIncludingGeneratedHeader();
|
||||
*/
|
||||
struct TestStruct : public JsonSerializable<TestStruct> {
|
||||
struct TestStruct : public JsonSerializable<TestStruct>, public BinarySerializable<TestStruct> {
|
||||
int someInt = 0;
|
||||
size_t someSize = 1;
|
||||
string someString = "foo";
|
||||
|
@ -35,6 +37,11 @@ private:
|
|||
string privateString = "not going to be serialized";
|
||||
};
|
||||
|
||||
// forward declarations shouldn't cause the generator to emit the code multiple times
|
||||
struct TestStruct;
|
||||
struct TestStruct;
|
||||
struct TestStruct;
|
||||
|
||||
class JsonGeneratorTests;
|
||||
|
||||
/*!
|
||||
|
@ -57,7 +64,7 @@ private:
|
|||
* \brief The AnotherTestStruct struct inherits from JsonSerializable and should hence have functional fromJson()
|
||||
* and toJson() methods. This is asserted in JsonGeneratorTests::testSingleInheritence();
|
||||
*/
|
||||
struct AnotherTestStruct : public JsonSerializable<AnotherTestStruct> {
|
||||
struct AnotherTestStruct : public JsonSerializable<AnotherTestStruct>, public BinarySerializable<AnotherTestStruct> {
|
||||
vector<string> arrayOfStrings{ "a", "b", "cd" };
|
||||
};
|
||||
|
||||
|
@ -65,7 +72,7 @@ struct AnotherTestStruct : public JsonSerializable<AnotherTestStruct> {
|
|||
* \brief The DerivedTestStruct struct inherits from JsonSerializable and should hence have functional fromJson()
|
||||
* and toJson() methods. This is asserted in JsonGeneratorTests::testInheritence();
|
||||
*/
|
||||
struct DerivedTestStruct : public TestStruct, public JsonSerializable<DerivedTestStruct> {
|
||||
struct DerivedTestStruct : public TestStruct, public JsonSerializable<DerivedTestStruct>, public BinarySerializable<DerivedTestStruct> {
|
||||
bool someBool = true;
|
||||
};
|
||||
|
||||
|
@ -92,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);
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -121,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,52 +1,44 @@
|
|||
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
|
||||
)
|
||||
set(SRC_FILES
|
||||
)
|
||||
set(TEST_HEADER_FILES
|
||||
)
|
||||
set(TEST_SRC_FILES
|
||||
tests/cppunit.cpp
|
||||
)
|
||||
set(CMAKE_MODULE_FILES
|
||||
cmake/modules/ReflectionGenerator.cmake
|
||||
)
|
||||
set(DOC_FILES
|
||||
README.md
|
||||
)
|
||||
set(HEADER_FILES traits.h versioning.h)
|
||||
set(SRC_FILES)
|
||||
set(TEST_HEADER_FILES)
|
||||
set(TEST_SRC_FILES)
|
||||
set(CMAKE_MODULE_FILES cmake/modules/ReflectionGenerator.cmake)
|
||||
set(DOC_FILES README.md)
|
||||
|
||||
# add JSON-specific sources
|
||||
if(RapidJSON_FOUND)
|
||||
list(APPEND HEADER_FILES
|
||||
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
|
||||
)
|
||||
endif()
|
||||
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)
|
||||
|
||||
# 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})
|
||||
list(APPEND PUBLIC_SHARED_INCLUDE_DIRS "${CPP_UTILITIES_INCLUDE_DIRS}")
|
||||
list(APPEND PUBLIC_STATIC_INCLUDE_DIRS "${CPP_UTILITIES_INCLUDE_DIRS}")
|
||||
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})
|
||||
endif()
|
||||
if (RapidJSON_FOUND)
|
||||
list(APPEND PUBLIC_INCLUDE_DIRS ${RAPIDJSON_INCLUDE_DIRS})
|
||||
endif ()
|
||||
|
||||
# include modules to apply configuration
|
||||
include(BasicConfig)
|
||||
|
@ -54,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)
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
#ifndef REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_BOOST_HANA_H
|
||||
#define REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_BOOST_HANA_H
|
||||
|
||||
/*!
|
||||
* \file reflector-boosthana.h
|
||||
* \brief Contains generic functions relying on Boost.Hana which can replace the code which would
|
||||
* otherwise had to be generated.
|
||||
* \remarks
|
||||
* These functions use boost::hana::keys() and boost::hana::at_key() rather than the "plain"
|
||||
* for-loop shown in the introspection examples of the Boost.Hana documentation. The reason is that
|
||||
* the "plain" for-loop involves making copies. This costs performance and - more importantly - prevents
|
||||
* modifying the actual object.
|
||||
*/
|
||||
|
||||
#include "./reflector.h"
|
||||
|
||||
#include <boost/hana/adapt_struct.hpp>
|
||||
#include <boost/hana/at_key.hpp>
|
||||
#include <boost/hana/define_struct.hpp>
|
||||
#include <boost/hana/for_each.hpp>
|
||||
#include <boost/hana/intersection.hpp>
|
||||
#include <boost/hana/keys.hpp>
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
namespace BinaryReflector {
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> *>
|
||||
BinaryVersion readCustomType(BinaryDeserializer &deserializer, Type &customType, BinaryVersion version)
|
||||
{
|
||||
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, BinaryVersion version)
|
||||
{
|
||||
boost::hana::for_each(boost::hana::keys(customType), [&](auto key) { serializer.write(boost::hana::at_key(customType, key), version); });
|
||||
}
|
||||
|
||||
} // namespace BinaryReflector
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_BOOST_HANA_H
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_CHRONO_UTILITIES_H
|
||||
#define REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_CHRONO_UTILITIES_H
|
||||
|
||||
/*!
|
||||
* \file reflector-chronoutilities.h
|
||||
* \brief Contains functions for (de)serializing objects from the chrono utilities provided by the
|
||||
* C++ utilities library.
|
||||
* \remarks This file demonstrates implementing custom (de)serialization for specific types.
|
||||
*/
|
||||
|
||||
#include "./reflector.h"
|
||||
|
||||
#include <c++utilities/chrono/datetime.h>
|
||||
#include <c++utilities/chrono/timespan.h>
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
namespace BinaryReflector {
|
||||
|
||||
template <>
|
||||
inline BinaryVersion readCustomType<CppUtilities::DateTime>(BinaryDeserializer &deserializer, CppUtilities::DateTime &dateTime, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
deserializer.read(dateTime.ticks());
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void writeCustomType<CppUtilities::DateTime>(BinarySerializer &serializer, const CppUtilities::DateTime &dateTime, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
serializer.write(dateTime.totalTicks());
|
||||
}
|
||||
|
||||
template <>
|
||||
inline BinaryVersion readCustomType<CppUtilities::TimeSpan>(BinaryDeserializer &deserializer, CppUtilities::TimeSpan &timeSpan, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
deserializer.read(timeSpan.ticks());
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void writeCustomType<CppUtilities::TimeSpan>(BinarySerializer &serializer, const CppUtilities::TimeSpan &timeSpan, BinaryVersion version)
|
||||
{
|
||||
CPP_UTILITIES_UNUSED(version)
|
||||
serializer.write(timeSpan.totalTicks());
|
||||
}
|
||||
|
||||
} // namespace BinaryReflector
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_CHRONO_UTILITIES_H
|
|
@ -0,0 +1,341 @@
|
|||
#ifndef REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_H
|
||||
#define REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_H
|
||||
|
||||
/*!
|
||||
* \file reflector.h
|
||||
* \brief Contains BinaryReader and BinaryWriter supporting binary (de)serialization
|
||||
* of primitive and custom types.
|
||||
*/
|
||||
|
||||
#include "../traits.h"
|
||||
#include "../versioning.h"
|
||||
|
||||
#include <c++utilities/conversion/conversionexception.h>
|
||||
#include <c++utilities/io/binaryreader.h>
|
||||
#include <c++utilities/io/binarywriter.h>
|
||||
|
||||
#include <any>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
/// \cond
|
||||
class BinaryReflectorTests;
|
||||
/// \endcond
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
/*!
|
||||
* \brief The AdaptedBinarySerializable class allows considering 3rd party classes as serializable.
|
||||
*/
|
||||
template <typename T> struct AdaptedBinarySerializable : public Traits::Bool<false> {
|
||||
static constexpr const char *name = "AdaptedBinarySerializable";
|
||||
static constexpr const char *qualifiedName = "ReflectiveRapidJSON::AdaptedBinarySerializable";
|
||||
};
|
||||
|
||||
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.
|
||||
*/
|
||||
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, 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;
|
||||
|
||||
/// \brief Reads \a customType via \a deserializer.
|
||||
/// \remarks
|
||||
/// - If \tp Type is versioned, the version is determined from the data. Otherwise \a version is assumed.
|
||||
/// - The determined or specified \a version shall be passed to nested invocations.
|
||||
/// \returns Returns the determined/assumed version.
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr>
|
||||
BinaryVersion readCustomType(BinaryDeserializer &deserializer, Type &customType, BinaryVersion version = 0);
|
||||
|
||||
/// \brief Writes \a customType via \a serializer.
|
||||
/// \remarks
|
||||
/// - If \tp Type is versioned, \a version is prepended to the data.
|
||||
/// - The specified \a version shall be passed to nested invocations.
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr>
|
||||
void writeCustomType(BinarySerializer &serializer, const Type &customType, BinaryVersion version = 0);
|
||||
|
||||
/// \brief The BinaryDeserializer class can read various data types, including custom ones, from an std::istream.
|
||||
class BinaryDeserializer : public CppUtilities::BinaryReader {
|
||||
friend class ::BinaryReflectorTests;
|
||||
|
||||
public:
|
||||
explicit BinaryDeserializer(std::istream *stream);
|
||||
|
||||
using CppUtilities::BinaryReader::read;
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> * = nullptr> void read(Type &pair);
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::unique_ptr>> * = nullptr> void read(Type &pointer);
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> * = nullptr> void read(Type &pointer);
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::optional>> * = nullptr> void read(Type &pointer);
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsResizable<Type>> * = nullptr> void read(Type &iteratable);
|
||||
template <typename Type, Traits::EnableIfAny<IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr> void read(Type &iteratable);
|
||||
template <typename Type,
|
||||
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 &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);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::uint64_t, std::any> m_pointer;
|
||||
};
|
||||
|
||||
/// \brief The BinarySerializer class can write various data types, including custom ones, to an std::ostream.
|
||||
class BinarySerializer : public CppUtilities::BinaryWriter {
|
||||
friend class ::BinaryReflectorTests;
|
||||
|
||||
public:
|
||||
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, std::optional>> * = nullptr>
|
||||
void write(const Type &pointer);
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::shared_ptr>> * = nullptr> void write(const Type &pointer);
|
||||
template <typename Type, Traits::EnableIf<IsIteratableExceptString<Type>, Traits::HasSize<Type>> * = nullptr> void write(const Type &iteratable);
|
||||
template <typename Type, Traits::EnableIf<std::is_enum<Type>> * = nullptr> void write(const Type &enumValue);
|
||||
template <typename Type, Traits::EnableIf<IsVariant<Type>> * = nullptr> void write(const Type &variant);
|
||||
template <typename Type, Traits::EnableIf<IsBuiltInType<Type>> * = nullptr> void write(const Type &builtInType, BinaryVersion version);
|
||||
template <typename Type, Traits::EnableIf<IsCustomType<Type>> * = nullptr> void write(const Type &customType, BinaryVersion version = 0);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::uint64_t, bool> m_pointer;
|
||||
};
|
||||
|
||||
inline BinaryDeserializer::BinaryDeserializer(std::istream *stream)
|
||||
: CppUtilities::BinaryReader(stream)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> *> void BinaryDeserializer::read(Type &pair)
|
||||
{
|
||||
read(pair.first);
|
||||
read(pair.second);
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::unique_ptr>> *> void BinaryDeserializer::read(Type &pointer)
|
||||
{
|
||||
if (!readBool()) {
|
||||
pointer.reset();
|
||||
return;
|
||||
}
|
||||
pointer = std::make_unique<typename Type::element_type>();
|
||||
read(*pointer);
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> *> void BinaryDeserializer::read(Type &pointer)
|
||||
{
|
||||
auto mode = readByte();
|
||||
if (!mode) {
|
||||
// pointer not set
|
||||
pointer.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto id = (mode & 0x4) ? readUInt64BE() : readVariableLengthUIntBE(); // the 3rd bit being flagged indicates a big ID
|
||||
if ((mode & 0x3) == 1) {
|
||||
// first occurrence: make a new pointer
|
||||
m_pointer[id] = pointer = std::make_shared<typename Type::element_type>();
|
||||
read(*pointer);
|
||||
return;
|
||||
}
|
||||
// further occurrences: copy previous pointer
|
||||
try {
|
||||
pointer = std::any_cast<Type>(m_pointer[id]);
|
||||
} catch (const std::bad_any_cast &) {
|
||||
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();
|
||||
iteratable.resize(size);
|
||||
for (auto &element : iteratable) {
|
||||
read(element);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIfAny<IsMapOrHash<Type>, IsMultiMapOrHash<Type>> *> void BinaryDeserializer::read(Type &iteratable)
|
||||
{
|
||||
const auto size = readVariableLengthUIntBE();
|
||||
for (size_t i = 0; i != size; ++i) {
|
||||
std::pair<typename std::remove_const<typename Type::value_type::first_type>::type, typename Type::value_type::second_type> value;
|
||||
read(value);
|
||||
iteratable.emplace(std::move(value));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Type,
|
||||
Traits::EnableIf<IsIteratableExceptString<Type>,
|
||||
Traits::None<IsMapOrHash<Type>, IsMultiMapOrHash<Type>, Traits::All<IsArray<Type>, Traits::IsResizable<Type>>>> *>
|
||||
void BinaryDeserializer::read(Type &iteratable)
|
||||
{
|
||||
const auto size = readVariableLengthUIntBE();
|
||||
for (size_t i = 0; i != size; ++i) {
|
||||
typename Type::value_type value;
|
||||
read(value);
|
||||
iteratable.emplace(std::move(value));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<std::is_enum<Type>> *> void BinaryDeserializer::read(Type &enumValue)
|
||||
{
|
||||
typename std::underlying_type<Type>::type value;
|
||||
read(value);
|
||||
enumValue = static_cast<Type>(value);
|
||||
}
|
||||
|
||||
/// \cond
|
||||
namespace Detail {
|
||||
template <typename Variant, std::size_t compiletimeIndex = 0>
|
||||
void readVariantValueByRuntimeIndex(std::size_t runtimeIndex, Variant &variant, BinaryDeserializer &deserializer)
|
||||
{
|
||||
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)
|
||||
: CppUtilities::BinaryWriter(stream)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::pair>> *> void BinarySerializer::write(const Type &pair)
|
||||
{
|
||||
write(pair.first);
|
||||
write(pair.second);
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::unique_ptr, std::optional>> *>
|
||||
void BinarySerializer::write(const Type &opt)
|
||||
{
|
||||
writeBool(static_cast<bool>(opt));
|
||||
if (opt) {
|
||||
write(*opt);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializingAnyOf<Type, std::shared_ptr>> *> void BinarySerializer::write(const Type &pointer)
|
||||
{
|
||||
if (pointer == nullptr) {
|
||||
writeByte(0);
|
||||
return;
|
||||
}
|
||||
const auto id = reinterpret_cast<std::uintptr_t>(pointer.get());
|
||||
const auto bigId = id >= 0x80000000000000;
|
||||
auto &alreadyWritten = m_pointer[id];
|
||||
std::uint8_t mode = alreadyWritten ? 2 : 1;
|
||||
if (bigId) {
|
||||
mode = mode | 0x4; // "flag" 3rd bit to indicate big ID
|
||||
}
|
||||
writeByte(mode);
|
||||
if (bigId) {
|
||||
writeUInt64BE(id);
|
||||
} else {
|
||||
writeVariableLengthUIntBE(id);
|
||||
}
|
||||
if (!alreadyWritten) {
|
||||
alreadyWritten = true;
|
||||
write(*pointer);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsIteratableExceptString<Type>, Traits::HasSize<Type>> *>
|
||||
void BinarySerializer::write(const Type &iteratable)
|
||||
{
|
||||
writeVariableLengthUIntBE(iteratable.size());
|
||||
for (const auto &element : iteratable) {
|
||||
write(element);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<std::is_enum<Type>> *> void BinarySerializer::write(const Type &enumValue)
|
||||
{
|
||||
write(static_cast<typename std::underlying_type<Type>::type>(enumValue));
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIf<IsVariant<Type>> *> void BinarySerializer::write(const Type &variant)
|
||||
{
|
||||
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
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_BINARY_REFLECTOR_H
|
|
@ -0,0 +1,65 @@
|
|||
#ifndef REFLECTIVE_RAPIDJSON_BINARY_SERIALIZABLE_H
|
||||
#define REFLECTIVE_RAPIDJSON_BINARY_SERIALIZABLE_H
|
||||
|
||||
/*!
|
||||
* \file serializable.h
|
||||
* \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.
|
||||
*/
|
||||
|
||||
#include "./reflector.h"
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
namespace ReflectiveRapidJSON {
|
||||
|
||||
using BinaryVersionNotSupported = VersionNotSupported<BinaryVersion>;
|
||||
|
||||
/*!
|
||||
* \brief The BinarySerializable class provides the CRTP-base for (de)serializable objects.
|
||||
*/
|
||||
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, BinaryVersion v> inline void BinarySerializable<Type, v>::toBinary(std::ostream &outputStream, BinaryVersion version) const
|
||||
{
|
||||
BinaryReflector::BinarySerializer(&outputStream).write(static_cast<const Type &>(*this), version);
|
||||
}
|
||||
|
||||
template <typename Type, BinaryVersion v> inline BinaryVersion BinarySerializable<Type, v>::restoreFromBinary(std::istream &inputStream)
|
||||
{
|
||||
return BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast<Type &>(*this));
|
||||
}
|
||||
|
||||
template <typename Type, BinaryVersion v> Type BinarySerializable<Type, v>::fromBinary(std::istream &inputStream)
|
||||
{
|
||||
Type object;
|
||||
static_cast<BinarySerializable<Type> &>(object).restoreFromBinary(inputStream);
|
||||
return object;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \def The REFLECTIVE_RAPIDJSON_MAKE_BINARY_SERIALIZABLE macro allows to adapt (de)serialization for types defined in 3rd party header files.
|
||||
* \remarks The struct will not have the toBinary() and fromBinary() methods available. Use the corresponding functions in the namespace
|
||||
* ReflectiveRapidJSON::BinaryReflector instead.
|
||||
* \todo GCC complains when putting :: before "ReflectiveRapidJSON" namespace: "global qualification of class name is invalid before ':' token"
|
||||
* 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> {}
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_BINARY_SERIALIZABLE_H
|
|
@ -1,132 +1,172 @@
|
|||
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)
|
||||
if (DEFINED REFLECTION_GENERATOR_MODULE_LOADED)
|
||||
return()
|
||||
endif()
|
||||
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")
|
||||
if(REFLECTION_GENERATOR_EXECUTABLE)
|
||||
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}")
|
||||
if (NOT EXISTS "${REFLECTION_GENERATOR_EXECUTABLE}" OR IS_DIRECTORY "${REFLECTION_GENERATOR_EXECUTABLE}")
|
||||
message(FATAL_ERROR "The specified code generator executable \"${REFLECTION_GENERATOR_EXECUTABLE}\" is not a file.")
|
||||
endif()
|
||||
elseif(CMAKE_CROSSCOMPILING OR NOT TARGET "${DEFAULT_REFLECTION_GENERATOR_EXECUTABLE}")
|
||||
endif ()
|
||||
elseif (CMAKE_CROSSCOMPILING OR NOT TARGET "${DEFAULT_REFLECTION_GENERATOR_EXECUTABLE}")
|
||||
# find native/external "reflective_rapidjson_generator"
|
||||
find_program(REFLECTION_GENERATOR_EXECUTABLE
|
||||
"${DEFAULT_REFLECTION_GENERATOR_EXECUTABLE}"
|
||||
PATHS "/usr/bin" "/bin"
|
||||
)
|
||||
else()
|
||||
find_program(REFLECTION_GENERATOR_EXECUTABLE "${DEFAULT_REFLECTION_GENERATOR_EXECUTABLE}" PATHS "/usr/bin" "/bin")
|
||||
else ()
|
||||
# use "reflective_rapidjson_generator" target
|
||||
set(REFLECTION_GENERATOR_EXECUTABLE "${DEFAULT_REFLECTION_GENERATOR_EXECUTABLE}")
|
||||
endif()
|
||||
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()
|
||||
endif ()
|
||||
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 "" 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
|
||||
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"
|
||||
)
|
||||
if(NOT REFLECTION_GENERATOR_CLANG_BIN)
|
||||
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)
|
||||
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()
|
||||
endif ()
|
||||
endif ()
|
||||
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)
|
||||
function (add_reflection_generator_invocation)
|
||||
# parse arguments
|
||||
set(OPTIONAL_ARGS
|
||||
)
|
||||
set(ONE_VALUE_ARGS
|
||||
OUTPUT_DIRECTORY
|
||||
JSON_VISIBILITY
|
||||
)
|
||||
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
|
||||
if(NOT ARGS_OUTPUT_DIRECTORY)
|
||||
if (NOT ARGS_OUTPUT_DIRECTORY)
|
||||
set(ARGS_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/reflection")
|
||||
file(MAKE_DIRECTORY "${ARGS_OUTPUT_DIRECTORY}")
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
# specify Clang's resource directory
|
||||
list(APPEND ARGS_CLANG_OPTIONS -resource-dir "${REFLECTION_GENERATOR_CLANG_RESOURCE_DIR}")
|
||||
|
||||
# apply specified REFLECTION_GENERATOR_TRIPLET
|
||||
if(REFLECTION_GENERATOR_TRIPLE)
|
||||
list(APPEND ARGS_CLANG_OPTIONS
|
||||
-Xclang -triple
|
||||
-Xclang "${REFLECTION_GENERATOR_TRIPLE}"
|
||||
)
|
||||
endif()
|
||||
if (REFLECTION_GENERATOR_TRIPLE)
|
||||
list(APPEND ARGS_CLANG_OPTIONS -Xclang -triple -Xclang "${REFLECTION_GENERATOR_TRIPLE}")
|
||||
endif ()
|
||||
|
||||
# apply specified REFLECTION_GENERATOR_INCLUDE_DIRECTORIES
|
||||
foreach(INCLUDE_DIR ${REFLECTION_GENERATOR_INCLUDE_DIRECTORIES})
|
||||
foreach (INCLUDE_DIR ${REFLECTION_GENERATOR_INCLUDE_DIRECTORIES})
|
||||
if (NOT IS_DIRECTORY "${INCLUDE_DIR}")
|
||||
message(FATAL_ERROR "Specified include directory \"${INCLUDE_DIR})\" for reflection generator doesn't exists.")
|
||||
endif ()
|
||||
list(APPEND ARGS_CLANG_OPTIONS -I "${INCLUDE_DIR}")
|
||||
endforeach()
|
||||
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
|
||||
)
|
||||
endif()
|
||||
# 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})
|
||||
# add compile flags
|
||||
set(PROP "$<TARGET_PROPERTY:${TARGET_NAME},COMPILE_FLAGS>")
|
||||
if (ARGS_CLANG_OPTIONS_FROM_TARGETS)
|
||||
foreach (TARGET_NAME ${ARGS_CLANG_OPTIONS_FROM_TARGETS})
|
||||
# set c++ standard
|
||||
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()
|
||||
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 ()
|
||||
|
||||
# create a custom command for each input file
|
||||
foreach(INPUT_FILE ${ARGS_INPUT_FILES})
|
||||
foreach (INPUT_FILE ${ARGS_INPUT_FILES})
|
||||
# determine the output file
|
||||
get_filename_component(OUTPUT_NAME "${INPUT_FILE}" NAME_WE)
|
||||
set(OUTPUT_FILE "${ARGS_OUTPUT_DIRECTORY}/${OUTPUT_NAME}.h")
|
||||
|
@ -134,34 +174,44 @@ function(add_reflection_generator_invocation)
|
|||
|
||||
# compose the CLI arguments and actually add the custom command
|
||||
set(CLI_ARGUMENTS
|
||||
--output-file "${OUTPUT_FILE}"
|
||||
--input-file "${INPUT_FILE}"
|
||||
--generators ${ARGS_GENERATORS}
|
||||
--clang-opt ${ARGS_CLANG_OPTIONS}
|
||||
--json-classes ${ARGS_JSON_CLASSES}
|
||||
)
|
||||
if(ARGS_JSON_VISIBILITY)
|
||||
--output-file
|
||||
"${OUTPUT_FILE}"
|
||||
--input-file
|
||||
"${INPUT_FILE}"
|
||||
--generators
|
||||
${ARGS_GENERATORS}
|
||||
--clang-opt
|
||||
${ARGS_CLANG_OPTIONS}
|
||||
--json-classes
|
||||
${ARGS_JSON_CLASSES})
|
||||
if (ARGS_JSON_VISIBILITY)
|
||||
list(APPEND CLI_ARGUMENTS --json-visibility "${ARGS_JSON_VISIBILITY}")
|
||||
endif()
|
||||
endif ()
|
||||
if (ARGS_BINARY_VISBILITY)
|
||||
list(APPEND CLI_ARGUMENTS --binary-visibility "${ARGS_BINARY_VISBILITY}")
|
||||
endif ()
|
||||
if (ARGS_ERROR_RESILIENT)
|
||||
list(APPEND CLI_ARGUMENTS --error-resilient)
|
||||
endif ()
|
||||
add_custom_command(
|
||||
OUTPUT "${OUTPUT_FILE}"
|
||||
COMMAND "${REFLECTION_GENERATOR_EXECUTABLE}"
|
||||
ARGS ${CLI_ARGUMENTS}
|
||||
COMMAND "${REFLECTION_GENERATOR_EXECUTABLE}" ARGS ${CLI_ARGUMENTS}
|
||||
DEPENDS "${INPUT_FILE}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMENT "Generating reflection code for ${INPUT_FILE}"
|
||||
VERBATIM
|
||||
)
|
||||
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)
|
||||
|
||||
# append the output file to lists specified via OUTPUT_LISTS
|
||||
if(ARGS_OUTPUT_LISTS)
|
||||
foreach(OUTPUT_LIST ${ARGS_OUTPUT_LISTS})
|
||||
if (ARGS_OUTPUT_LISTS)
|
||||
foreach (OUTPUT_LIST ${ARGS_OUTPUT_LISTS})
|
||||
list(APPEND "${OUTPUT_LIST}" "${OUTPUT_FILE}")
|
||||
set("${OUTPUT_LIST}" "${${OUTPUT_LIST}}" PARENT_SCOPE)
|
||||
endforeach()
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
set("${OUTPUT_LIST}"
|
||||
"${${OUTPUT_LIST}}"
|
||||
PARENT_SCOPE)
|
||||
endforeach ()
|
||||
endif ()
|
||||
endforeach ()
|
||||
endfunction ()
|
||||
|
|
|
@ -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,17 +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,
|
||||
|
@ -43,18 +46,18 @@ enum class JsonType : byte {
|
|||
// define helper functions which return the JsonType for the C++ type specified as template parameter
|
||||
|
||||
template <typename Type,
|
||||
Traits::EnableIf<Traits::Not<std::is_same<Type, bool>>, Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>>>...>
|
||||
Traits::EnableIf<Traits::Not<std::is_same<Type, bool>>, Traits::Any<std::is_integral<Type>, std::is_floating_point<Type>>> * = nullptr>
|
||||
constexpr JsonType jsonType()
|
||||
{
|
||||
return JsonType::Number;
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIfAny<std::is_same<Type, bool>>...> constexpr JsonType jsonType()
|
||||
template <typename Type, Traits::EnableIfAny<std::is_same<Type, bool>> * = nullptr> constexpr JsonType jsonType()
|
||||
{
|
||||
return JsonType::Bool;
|
||||
}
|
||||
|
||||
template <typename Type, Traits::EnableIfAny<Traits::IsString<Type>, Traits::IsCString<Type>>...> constexpr JsonType jsonType()
|
||||
template <typename Type, Traits::EnableIfAny<Traits::IsString<Type>, Traits::IsCString<Type>> * = nullptr> constexpr JsonType jsonType()
|
||||
{
|
||||
return JsonType::String;
|
||||
}
|
||||
|
@ -62,7 +65,7 @@ template <typename Type, Traits::EnableIfAny<Traits::IsString<Type>, Traits::IsC
|
|||
template <typename Type,
|
||||
Traits::EnableIf<Traits::IsIteratable<Type>,
|
||||
Traits::Not<Traits::Any<Traits::IsString<Type>, Traits::IsSpecializationOf<Type, std::map>,
|
||||
Traits::IsSpecializationOf<Type, std::unordered_map>>>>...>
|
||||
Traits::IsSpecializationOf<Type, std::unordered_map>>>> * = nullptr>
|
||||
constexpr JsonType jsonType()
|
||||
{
|
||||
return JsonType::Array;
|
||||
|
@ -72,7 +75,7 @@ template <typename Type,
|
|||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, Traits::IsString<Type>, Traits::IsCString<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>,
|
||||
Traits::Not<Traits::Any<Traits::IsString<Type>, Traits::IsSpecializationOf<Type, std::map>,
|
||||
Traits::IsSpecializationOf<Type, std::unordered_map>>>>>...>
|
||||
Traits::IsSpecializationOf<Type, std::unordered_map>>>>> * = nullptr>
|
||||
constexpr JsonType jsonType()
|
||||
{
|
||||
return JsonType::Object;
|
||||
|
@ -107,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;
|
||||
|
@ -120,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();
|
||||
};
|
||||
|
||||
|
@ -152,8 +155,10 @@ 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);
|
||||
|
||||
/// \brief The name of the class or struct which is currently being processed.
|
||||
const char *currentRecord;
|
||||
|
@ -162,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 } 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;
|
||||
|
@ -184,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));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -194,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();
|
||||
}
|
||||
}
|
||||
|
@ -209,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.
|
||||
|
@ -231,6 +253,17 @@ inline void JsonDeserializationErrors::reportConversionError(JsonType jsonType)
|
|||
throwMaybe(ThrowOn::ConversionError);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Reports an unexpected duplicate. An error of that kind occurs when the JSON type matched the expected type, but the value can not be inserted in the container because it is already present and duplicates are not allowed.
|
||||
* \todo Allow specifying the error message.
|
||||
* \remarks This can happen when doing custom mapping (eg. when interpreting a JSON string as time value).
|
||||
*/
|
||||
inline void JsonDeserializationErrors::reportUnexpectedDuplicate(JsonType jsonType)
|
||||
{
|
||||
emplace_back(JsonDeserializationErrorKind::UnexpectedDuplicate, jsonType, jsonType, currentRecord, currentMember, currentIndex);
|
||||
throwMaybe(ThrowOn::UnexpectedDuplicate);
|
||||
}
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_JSON_REFLECTOR_H
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace JsonReflector {
|
|||
|
||||
// define function to "push" values to a RapidJSON array or object
|
||||
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>>...>
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> *>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
boost::hana::for_each(boost::hana::keys(reflectable), [&reflectable, &value, &allocator](auto key) {
|
||||
|
@ -36,7 +36,7 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Object &value, RA
|
|||
|
||||
// define function to "pull" values from a RapidJSON array or object
|
||||
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>>...>
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> *>
|
||||
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value,
|
||||
JsonDeserializationErrors *errors)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
* std::vector, ... with RapidJSON.
|
||||
*/
|
||||
|
||||
#include <c++utilities/conversion/types.h>
|
||||
#include <c++utilities/misc/traits.h>
|
||||
#include "../traits.h"
|
||||
|
||||
#include <c++utilities/application/global.h>
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/rapidjson.h>
|
||||
|
@ -18,9 +19,14 @@
|
|||
#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"
|
||||
|
||||
|
@ -77,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
|
||||
|
@ -87,44 +93,37 @@ template <typename Type>
|
|||
using IsJsonSerializable
|
||||
= Traits::Any<Traits::Not<Traits::IsComplete<Type>>, std::is_base_of<JsonSerializable<Type>, Type>, AdaptedJsonSerializable<Type>>;
|
||||
|
||||
// define trait to check for map or hash
|
||||
template <typename Type>
|
||||
using IsMapOrHash = Traits::Any<Traits::IsSpecializationOf<Type, std::map>, Traits::IsSpecializationOf<Type, std::unordered_map>>;
|
||||
template <typename Type>
|
||||
using IsArray
|
||||
= Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>, Traits::Not<IsMapOrHash<Type>>>;
|
||||
|
||||
// define functions to "push" values to a RapidJSON array or object
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified \a reflectable to the specified value.
|
||||
*/
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>>...>
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> * = nullptr>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);
|
||||
|
||||
/*!
|
||||
* \brief Pushes the \a reflectable to the specified array.
|
||||
*/
|
||||
template <typename Type, Traits::DisableIf<IsJsonSerializable<Type>>...>
|
||||
template <typename Type, Traits::DisableIf<IsJsonSerializable<Type>> * = nullptr>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);
|
||||
|
||||
/*!
|
||||
* \brief Pushes the \a reflectable which has a custom type to the specified array.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsJsonSerializable<Type>>...>
|
||||
template <typename Type, Traits::EnableIf<IsJsonSerializable<Type>> * = nullptr>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified \a reflectable which has custom type as a member to the specified object.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsJsonSerializable<Type>>...>
|
||||
template <typename Type, Traits::EnableIf<IsJsonSerializable<Type>> * = nullptr>
|
||||
void push(
|
||||
const Type &reflectable, const char *name, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified \a reflectable as a member to the specified object.
|
||||
*/
|
||||
template <typename Type, Traits::DisableIf<IsJsonSerializable<Type>>...>
|
||||
template <typename Type, Traits::DisableIf<IsJsonSerializable<Type>> * = nullptr>
|
||||
void push(
|
||||
const Type &reflectable, const char *name, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);
|
||||
|
||||
|
@ -132,13 +131,13 @@ void push(
|
|||
* \brief Pushes the \a reflectable which has a custom type to the specified object.
|
||||
* \remarks The definition of this function must be provided by the code generator or Boost.Hana.
|
||||
*/
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>>...>
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> * = nullptr>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified \a reflectable which has a custom type to the specified value.
|
||||
*/
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>>...>
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> *>
|
||||
inline void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
value.SetObject();
|
||||
|
@ -149,57 +148,78 @@ 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>>...>
|
||||
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 enumeration item to the specified value.
|
||||
* \brief Pushes the specified 8-bit integer to the specified value.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_enum<Type>>...>
|
||||
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<Traits::Conditional<std::is_unsigned<typename std::underlying_type<Type>::type>, uint64, int64>>(reflectable), 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>, 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 *>>...>
|
||||
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 &>>...>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified std::string to the specified value.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>>...>
|
||||
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);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified iteratable (eg. std::vector, std::list) to the specified value.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::HasSize<Type>>...>
|
||||
template <typename Type, Traits::EnableIf<IsArrayOrSet<Type>, Traits::HasSize<Type>> * = 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(reflectable.size(), allocator);
|
||||
array.Reserve(rapidJsonSize(reflectable.size()), allocator);
|
||||
for (const auto &item : reflectable) {
|
||||
push(item, array, allocator);
|
||||
}
|
||||
|
@ -208,7 +228,7 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
|
|||
/*!
|
||||
* \brief Pushes the specified iteratable list (eg. std::vector, std::list) to the specified value.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::Not<Traits::HasSize<Type>>>...>
|
||||
template <typename Type, Traits::EnableIf<IsArrayOrSet<Type>, Traits::Not<Traits::HasSize<Type>>> * = nullptr>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
value.SetArray();
|
||||
|
@ -221,7 +241,7 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_
|
|||
/*!
|
||||
* \brief Pushes the specified map (std::map, std::unordered_map) to the specified value.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsMapOrHash<Type>>...>
|
||||
template <typename Type, Traits::EnableIfAny<IsMapOrHash<Type>> * = nullptr>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
value.SetObject();
|
||||
|
@ -231,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 {
|
||||
|
||||
/*!
|
||||
|
@ -255,7 +296,7 @@ template <class Tuple> struct TuplePushHelper<Tuple, 1> {
|
|||
/*!
|
||||
* \brief Pushes the specified tuple to the specified value.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>>...>
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>> * = nullptr>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
value.SetArray();
|
||||
|
@ -265,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>>...>
|
||||
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) {
|
||||
|
@ -279,10 +332,39 @@ 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.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsJsonSerializable<Type>>...>
|
||||
template <typename Type, Traits::EnableIf<IsJsonSerializable<Type>> *>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Value objectValue(RAPIDJSON_NAMESPACE::kObjectType);
|
||||
|
@ -294,7 +376,7 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAP
|
|||
/*!
|
||||
* \brief Pushes the specified \a reflectable to the specified array.
|
||||
*/
|
||||
template <typename Type, Traits::DisableIf<IsJsonSerializable<Type>>...>
|
||||
template <typename Type, Traits::DisableIf<IsJsonSerializable<Type>> *>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Value genericValue;
|
||||
|
@ -305,7 +387,7 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAP
|
|||
/*!
|
||||
* \brief Pushes the specified \a reflectable which has custom type as a member to the specified object.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsJsonSerializable<Type>>...>
|
||||
template <typename Type, Traits::EnableIf<IsJsonSerializable<Type>> *>
|
||||
void push(
|
||||
const Type &reflectable, const char *name, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
|
@ -318,7 +400,7 @@ void push(
|
|||
/*!
|
||||
* \brief Pushes the specified \a reflectable as a member to the specified object.
|
||||
*/
|
||||
template <typename Type, Traits::DisableIf<IsJsonSerializable<Type>>...>
|
||||
template <typename Type, Traits::DisableIf<IsJsonSerializable<Type>> *>
|
||||
void push(
|
||||
const Type &reflectable, const char *name, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
|
@ -333,56 +415,92 @@ void push(
|
|||
* \brief Pulls the \a reflectable which has a custom type from the specified object.
|
||||
* \remarks The definition of this function must be provided by the code generator or Boost.Hana.
|
||||
*/
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>>...>
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> * = nullptr>
|
||||
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value,
|
||||
JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the \a reflectable which has a custom type from the specified value which is supposed and checked to contain an object.
|
||||
*/
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>>...>
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> * = nullptr>
|
||||
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is an iteratable without reserve() method from the specified value which is checked to contain an array.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::Not<Traits::IsReservable<Type>>>...>
|
||||
template <typename Type, Traits::EnableIf<IsArrayOrSet<Type>, Traits::Not<Traits::IsReservable<Type>>> * = nullptr>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is an iteratable with reserve() method from the specified value which is checked to contain an array.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsReservable<Type>>...>
|
||||
template <typename Type, Traits::EnableIf<IsArrayOrSet<Type>, Traits::IsReservable<Type>> * = nullptr>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is an iteratable from the specified array. The \a reflectable is cleared before.
|
||||
* \brief Pulls the specified \a reflectable which is an array/vector/list from the specified array. The \a reflectable is cleared before.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>>...>
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>> * = nullptr>
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a set from the specified array. The \a reflectable is cleared before.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsSet<Type>> * = nullptr>
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a multiset from the specified array. The \a reflectable is cleared before.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsMultiSet<Type>> * = nullptr>
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a map from the specified value which is checked to contain an object.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsMapOrHash<Type>>...>
|
||||
template <typename Type, Traits::EnableIf<IsMapOrHash<Type>> * = nullptr>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a multimap from the specified value which is checked to contain an object.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsMultiMapOrHash<Type>> * = nullptr>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a tuple from the specified value which is checked to contain an array.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>>...>
|
||||
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.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::unique_ptr>>...>
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::unique_ptr>> * = nullptr>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a shared_ptr from the specified value which might be null.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>>...>
|
||||
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);
|
||||
|
||||
/*!
|
||||
|
@ -404,14 +522,15 @@ inline void pull(Type &reflectable, const char *name, const rapidjson::GenericVa
|
|||
/*!
|
||||
* \brief Pulls the \a reflectable which has a custom type from the specified value which is supposed and checked to contain an object.
|
||||
*/
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>>...>
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> *>
|
||||
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \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>>>...>
|
||||
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)
|
||||
{
|
||||
|
@ -424,10 +543,24 @@ 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.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<std::is_same<Type, bool>>...>
|
||||
template <typename Type, Traits::EnableIf<std::is_same<Type, bool>> * = nullptr>
|
||||
inline void pull(
|
||||
Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
|
@ -444,11 +577,11 @@ inline void pull(
|
|||
* \brief Pulls the specified enumeration item from the specified value which is supposed and checked to be compatible with the underlying type.
|
||||
* \remarks It is *not* checked, whether \a value is actually a valid enum item.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_enum<Type>>...>
|
||||
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());
|
||||
|
@ -461,7 +594,7 @@ inline void pull(
|
|||
/*!
|
||||
* \brief Pulls the std::string from the specified value which is supposed and checked to contain a string.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>>...>
|
||||
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>> * = nullptr>
|
||||
inline void pull(
|
||||
Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
|
@ -478,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 &>>...>
|
||||
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()) {
|
||||
|
@ -492,7 +626,7 @@ inline void pull(Type &, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMES
|
|||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is an iteratable without reserve() method from the specified value which is checked to contain an array.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::Not<Traits::IsReservable<Type>>>...>
|
||||
template <typename Type, Traits::EnableIf<IsArrayOrSet<Type>, Traits::Not<Traits::IsReservable<Type>>> *>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
if (!value.IsArray()) {
|
||||
|
@ -507,7 +641,7 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
|
|||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is an iteratable with reserve() method from the specified value which is checked to contain an array.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>, Traits::IsReservable<Type>>...>
|
||||
template <typename Type, Traits::EnableIf<IsArrayOrSet<Type>, Traits::IsReservable<Type>> *>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
if (!value.IsArray()) {
|
||||
|
@ -522,9 +656,9 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is an iteratable from the specified array. The \a reflectable is cleared before.
|
||||
* \brief Pulls the specified \a reflectable which is an array/vector/list from the specified array. The \a reflectable is cleared before.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>>...>
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>> *>
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors)
|
||||
{
|
||||
// clear previous contents of the array
|
||||
|
@ -537,9 +671,67 @@ void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<c
|
|||
if (errors) {
|
||||
errors->currentIndex = index;
|
||||
}
|
||||
++index;
|
||||
reflectable.emplace_back();
|
||||
pull(reflectable.back(), item, errors);
|
||||
}
|
||||
|
||||
// clear error context
|
||||
if (errors) {
|
||||
errors->currentIndex = JsonDeserializationError::noIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a multiset from the specified array. The \a reflectable is cleared before.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsMultiSet<Type>> *>
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors)
|
||||
{
|
||||
// clear previous contents of the array
|
||||
reflectable.clear();
|
||||
|
||||
// pull all array elements of the specified value
|
||||
std::size_t index = 0;
|
||||
for (const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &item : array) {
|
||||
// set error context for current index
|
||||
if (errors) {
|
||||
errors->currentIndex = index;
|
||||
}
|
||||
++index;
|
||||
typename Type::value_type itemObj;
|
||||
pull(itemObj, item, errors);
|
||||
reflectable.emplace(std::move(itemObj));
|
||||
}
|
||||
|
||||
// clear error context
|
||||
if (errors) {
|
||||
errors->currentIndex = JsonDeserializationError::noIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a set from the specified array. The \a reflectable is cleared before.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsSet<Type>> *>
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors)
|
||||
{
|
||||
// clear previous contents of the array
|
||||
reflectable.clear();
|
||||
|
||||
// pull all array elements of the specified value
|
||||
std::size_t index = 0;
|
||||
for (const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &item : array) {
|
||||
// set error context for current index
|
||||
if (errors) {
|
||||
errors->currentIndex = index;
|
||||
}
|
||||
++index;
|
||||
typename Type::value_type itemObj;
|
||||
pull(itemObj, item, errors);
|
||||
if (!reflectable.emplace(std::move(itemObj)).second) {
|
||||
errors->reportUnexpectedDuplicate(JsonType::Array);
|
||||
}
|
||||
}
|
||||
|
||||
// clear error context
|
||||
|
@ -551,7 +743,7 @@ void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<c
|
|||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a map from the specified value which is checked to contain an object.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsMapOrHash<Type>>...>
|
||||
template <typename Type, Traits::EnableIf<IsMapOrHash<Type>> *>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
if (!value.IsObject()) {
|
||||
|
@ -566,6 +758,33 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a multimap from the specified value which is checked to contain an object.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsMultiMapOrHash<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();
|
||||
for (auto i = obj.MemberBegin(), end = obj.MemberEnd(); i != end; ++i) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Detail {
|
||||
|
||||
/*!
|
||||
|
@ -591,7 +810,7 @@ template <class Tuple> struct TuplePullHelper<Tuple, 1> {
|
|||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a tuple from the specified value which is checked to contain an array.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>>...>
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::tuple>> *>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
if (!value.IsArray()) {
|
||||
|
@ -600,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
|
||||
|
@ -611,10 +830,34 @@ 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.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::unique_ptr>>...>
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::unique_ptr>> *>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
if (value.IsNull()) {
|
||||
|
@ -628,7 +871,7 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
|
|||
/*!
|
||||
* \brief Pulls the specified \a reflectable which is a shared_ptr from the specified value which might be null.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>>...>
|
||||
template <typename Type, Traits::EnableIf<Traits::IsSpecializationOf<Type, std::shared_ptr>> *>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
if (value.IsNull()) {
|
||||
|
@ -639,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.
|
||||
*/
|
||||
|
@ -665,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;
|
||||
|
@ -683,7 +1000,7 @@ inline void pull(Type &reflectable, const char *name, const rapidjson::GenericVa
|
|||
/*!
|
||||
* \brief Pulls the \a reflectable which has a custom type from the specified value which is supposed and checked to contain an object.
|
||||
*/
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>>...>
|
||||
template <typename Type, Traits::DisableIf<IsBuiltInType<Type>> *>
|
||||
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
{
|
||||
if (!value.IsObject()) {
|
||||
|
@ -700,54 +1017,77 @@ void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_N
|
|||
/*!
|
||||
* \brief Serializes the specified \a reflectable which has a custom type or can be mapped to and object.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<IsJsonSerializable<Type>, IsMapOrHash<Type>>...>
|
||||
RAPIDJSON_NAMESPACE::StringBuffer toJson(const Type &reflectable)
|
||||
template <typename Type, Traits::EnableIfAny<IsJsonSerializable<Type>, IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr>
|
||||
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());
|
||||
return serializeJsonDocToString(document);
|
||||
push(reflectable, document, document.GetAllocator());
|
||||
return document;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Serializes the specified \a reflectable which is an integer, float or boolean.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>>...>
|
||||
RAPIDJSON_NAMESPACE::StringBuffer toJson(Type reflectable)
|
||||
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>> * = nullptr>
|
||||
RAPIDJSON_NAMESPACE::Document toJsonDocument(Type reflectable)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kNumberType);
|
||||
document.Set(reflectable, document.GetAllocator());
|
||||
return serializeJsonDocToString(document);
|
||||
return document;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Serializes the specified \a reflectable which is an std::string.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>>...>
|
||||
RAPIDJSON_NAMESPACE::StringBuffer toJson(const std::string &reflectable)
|
||||
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>> * = nullptr>
|
||||
RAPIDJSON_NAMESPACE::Document toJsonDocument(const std::string &reflectable)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kStringType);
|
||||
document.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), document.GetAllocator());
|
||||
return serializeJsonDocToString(document);
|
||||
return document;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Serializes the specified \a reflectable which is a C-string.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<std::is_same<Type, const char *>>...> RAPIDJSON_NAMESPACE::StringBuffer toJson(const char *reflectable)
|
||||
template <typename Type, Traits::EnableIf<std::is_same<Type, const char *>> * = nullptr>
|
||||
RAPIDJSON_NAMESPACE::Document toJsonDocument(const char *reflectable)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kStringType);
|
||||
document.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable), document.GetAllocator());
|
||||
return serializeJsonDocToString(document);
|
||||
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.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>>...> RAPIDJSON_NAMESPACE::StringBuffer toJson(const Type &reflectable)
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>> * = nullptr> RAPIDJSON_NAMESPACE::Document toJsonDocument(const Type &reflectable)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kArrayType);
|
||||
push(reflectable, document, document.GetAllocator());
|
||||
return document;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Serializes the specified \a reflectable.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::EnableIfAny<IsJsonSerializable<Type>, IsMapOrHash<Type>, IsMultiMapOrHash<Type>, std::is_integral<Type>, std::is_floating_point<Type>,
|
||||
Traits::IsString<Type>, IsArray<Type>> * = nullptr>
|
||||
RAPIDJSON_NAMESPACE::StringBuffer toJson(const Type &reflectable)
|
||||
{
|
||||
auto document(toJsonDocument(reflectable));
|
||||
return serializeJsonDocToString(document);
|
||||
}
|
||||
|
||||
|
@ -756,7 +1096,7 @@ template <typename Type, Traits::EnableIf<IsArray<Type>>...> RAPIDJSON_NAMESPACE
|
|||
/*!
|
||||
* \brief Deserializes the specified JSON to \tparam Type which is a custom type or can be mapped to an object.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<IsJsonSerializable<Type>, IsMapOrHash<Type>>...>
|
||||
template <typename Type, Traits::EnableIfAny<IsJsonSerializable<Type>, IsMapOrHash<Type>, IsMultiMapOrHash<Type>> * = nullptr>
|
||||
Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors = nullptr)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document doc(parseJsonDocFromString(json, jsonSize));
|
||||
|
@ -775,7 +1115,7 @@ Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors
|
|||
/*!
|
||||
* \brief Deserializes the specified JSON to \tparam Type which is an integer, float or boolean.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>>...>
|
||||
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>> * = nullptr>
|
||||
Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors = nullptr)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document doc(parseJsonDocFromString(json, jsonSize));
|
||||
|
@ -792,7 +1132,7 @@ Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors
|
|||
/*!
|
||||
* \brief Deserializes the specified JSON to \tparam Type which is a std::string.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>>...>
|
||||
template <typename Type, Traits::EnableIf<std::is_same<Type, std::string>> * = nullptr>
|
||||
Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors = nullptr)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document doc(parseJsonDocFromString(json, jsonSize));
|
||||
|
@ -809,7 +1149,7 @@ Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors
|
|||
/*!
|
||||
* \brief Deserializes the specified JSON to \tparam Type which can be mapped to an array.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>>...>
|
||||
template <typename Type, Traits::EnableIf<IsArray<Type>> * = nullptr>
|
||||
Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors = nullptr)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document doc(parseJsonDocFromString(json, jsonSize));
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
||||
|
@ -25,11 +25,16 @@ template <typename Type> struct JsonSerializable {
|
|||
|
||||
// high-level API
|
||||
RAPIDJSON_NAMESPACE::StringBuffer toJson() const;
|
||||
RAPIDJSON_NAMESPACE::Document toJsonDocument() const;
|
||||
static Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors = nullptr);
|
||||
static Type fromJson(const char *json, JsonDeserializationErrors *errors = nullptr);
|
||||
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
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -49,7 +54,7 @@ template <typename Type> void JsonSerializable<Type>::push(RAPIDJSON_NAMESPACE::
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Converts the object to its JSON representation.
|
||||
* \brief Converts the object to its JSON representation (rapidjson::StringBuffer).
|
||||
* \remarks To obtain a string from the returned buffer, just use its GetString() method.
|
||||
*/
|
||||
template <typename Type> RAPIDJSON_NAMESPACE::StringBuffer JsonSerializable<Type>::toJson() const
|
||||
|
@ -57,6 +62,15 @@ template <typename Type> RAPIDJSON_NAMESPACE::StringBuffer JsonSerializable<Type
|
|||
return JsonReflector::toJson<Type>(static_cast<const Type &>(*this));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Converts the object to its JSON representation (rapidjson::Document).
|
||||
* \remarks To obtain a string from the returned buffer, just use its GetString() method.
|
||||
*/
|
||||
template <typename Type> RAPIDJSON_NAMESPACE::Document JsonSerializable<Type>::toJsonDocument() const
|
||||
{
|
||||
return JsonReflector::toJsonDocument<Type>(static_cast<const Type &>(*this));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Constructs a new object from the specified JSON.
|
||||
*/
|
||||
|
@ -84,7 +98,7 @@ template <typename Type> Type JsonSerializable<Type>::fromJson(const std::string
|
|||
/*!
|
||||
* \brief Helps to disambiguate when inheritance is used.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<std::is_base_of<JsonSerializable<Type>, Type>>...> JsonSerializable<Type> &as(Type &serializable)
|
||||
template <typename Type, Traits::EnableIf<std::is_base_of<JsonSerializable<Type>, Type>> * = nullptr> JsonSerializable<Type> &as(Type &serializable)
|
||||
{
|
||||
return static_cast<JsonSerializable<Type> &>(serializable);
|
||||
}
|
||||
|
@ -92,7 +106,7 @@ template <typename Type, Traits::EnableIf<std::is_base_of<JsonSerializable<Type>
|
|||
/*!
|
||||
* \brief Helps to disambiguate when inheritance is used.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<std::is_base_of<JsonSerializable<Type>, Type>>...>
|
||||
template <typename Type, Traits::EnableIf<std::is_base_of<JsonSerializable<Type>, Type>> * = nullptr>
|
||||
const JsonSerializable<Type> &as(const Type &serializable)
|
||||
{
|
||||
return static_cast<const JsonSerializable<Type> &>(serializable);
|
||||
|
@ -106,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.
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
#include "../binary/reflector-boosthana.h"
|
||||
#include "../binary/serializable.h"
|
||||
|
||||
#include <c++utilities/conversion/stringbuilder.h>
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
#include <c++utilities/io/misc.h>
|
||||
#include <c++utilities/tests/testutils.h>
|
||||
|
||||
using CppUtilities::operator<<; // must be visible prior to the call site
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
#include <rapidjson/writer.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using namespace CPPUNIT_NS;
|
||||
using namespace RAPIDJSON_NAMESPACE;
|
||||
using namespace CppUtilities;
|
||||
using namespace CppUtilities::Literals;
|
||||
using namespace ReflectiveRapidJSON;
|
||||
|
||||
/// \cond
|
||||
|
||||
// define some structs for testing serialization
|
||||
struct TestObjectBinaryHana : public BinarySerializable<TestObjectBinaryHana> {
|
||||
BOOST_HANA_DEFINE_STRUCT(TestObjectBinaryHana, (int, number), (double, number2), (vector<int>, numbers), (string, text), (bool, boolean));
|
||||
};
|
||||
|
||||
struct NestingArrayBinaryHana : public BinarySerializable<NestingArrayBinaryHana> {
|
||||
BOOST_HANA_DEFINE_STRUCT(NestingArrayBinaryHana, (string, name), (vector<TestObjectBinaryHana>, testObjects));
|
||||
};
|
||||
|
||||
/// \endcond
|
||||
|
||||
/*!
|
||||
* \brief The BinaryReflectorBoostHanaTests class tests the integration of Boost.Hana with the (de)serializer.
|
||||
* \remarks In these tests, the reflection is provided through Boost.Hana so the code generator is not involved.
|
||||
*/
|
||||
class BinaryReflectorBoostHanaTests : public TestFixture {
|
||||
CPPUNIT_TEST_SUITE(BinaryReflectorBoostHanaTests);
|
||||
CPPUNIT_TEST(testSerializingAndDeserializing);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp() override;
|
||||
void tearDown() override;
|
||||
|
||||
void testSerializingAndDeserializing();
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(BinaryReflectorBoostHanaTests);
|
||||
|
||||
void BinaryReflectorBoostHanaTests::setUp()
|
||||
{
|
||||
}
|
||||
|
||||
void BinaryReflectorBoostHanaTests::tearDown()
|
||||
{
|
||||
}
|
||||
|
||||
void BinaryReflectorBoostHanaTests::testSerializingAndDeserializing()
|
||||
{
|
||||
TestObjectBinaryHana testObject;
|
||||
testObject.number = 42;
|
||||
testObject.number2 = 1234.25;
|
||||
testObject.numbers = { 1, 2, 3, 4, 5 };
|
||||
testObject.text = "foo";
|
||||
testObject.boolean = true;
|
||||
|
||||
NestingArrayBinaryHana nestingObject;
|
||||
nestingObject.name = "bar";
|
||||
nestingObject.testObjects.emplace_back(testObject);
|
||||
|
||||
stringstream stream(ios_base::in | ios_base::out | ios_base::binary);
|
||||
stream.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
nestingObject.toBinary(stream);
|
||||
|
||||
const auto deserializedObject(NestingArrayBinaryHana::fromBinary(stream));
|
||||
const auto &deserializedTestObj(deserializedObject.testObjects.at(0));
|
||||
CPPUNIT_ASSERT_EQUAL(nestingObject.name, deserializedObject.name);
|
||||
CPPUNIT_ASSERT_EQUAL(testObject.number, deserializedTestObj.number);
|
||||
CPPUNIT_ASSERT_EQUAL(testObject.number2, deserializedTestObj.number2);
|
||||
CPPUNIT_ASSERT_EQUAL(testObject.numbers, deserializedTestObj.numbers);
|
||||
CPPUNIT_ASSERT_EQUAL(testObject.text, deserializedTestObj.text);
|
||||
CPPUNIT_ASSERT_EQUAL(testObject.boolean, deserializedTestObj.boolean);
|
||||
}
|
|
@ -0,0 +1,452 @@
|
|||
#include "../binary/reflector-chronoutilities.h"
|
||||
#include "../binary/reflector.h"
|
||||
#include "../binary/serializable.h"
|
||||
|
||||
#include <c++utilities/conversion/stringbuilder.h>
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
#include <c++utilities/io/misc.h>
|
||||
#include <c++utilities/tests/testutils.h>
|
||||
|
||||
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>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using namespace CPPUNIT_NS;
|
||||
using namespace CppUtilities;
|
||||
using namespace CppUtilities::Literals;
|
||||
using namespace ReflectiveRapidJSON;
|
||||
|
||||
/// \cond
|
||||
|
||||
// define some enums and structs for testing serialization
|
||||
enum SomeEnumBinary {
|
||||
SomeEnumItem1,
|
||||
SomeEnumItem2,
|
||||
SomeEnumItem3,
|
||||
};
|
||||
|
||||
enum class SomeEnumClassBinary : std::uint16_t {
|
||||
Item1,
|
||||
Item2,
|
||||
Item3,
|
||||
};
|
||||
|
||||
struct TestObjectBinary : public BinarySerializable<TestObjectBinary> {
|
||||
int number;
|
||||
double number2;
|
||||
vector<int> numbers;
|
||||
string text;
|
||||
bool boolean;
|
||||
map<string, int> someMap;
|
||||
unordered_map<string, bool> someHash;
|
||||
set<string> someSet;
|
||||
multiset<string> someMultiset;
|
||||
unordered_set<string> someUnorderedSet;
|
||||
unordered_multiset<string> someUnorderedMultiset;
|
||||
SomeEnumBinary someEnum;
|
||||
SomeEnumClassBinary someEnumClass;
|
||||
TimeSpan timeSpan;
|
||||
DateTime dateTime;
|
||||
};
|
||||
|
||||
struct NestingArrayBinary : public BinarySerializable<NestingArrayBinary> {
|
||||
string name;
|
||||
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 <> 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);
|
||||
deserializer.read(customType.text);
|
||||
deserializer.read(customType.boolean);
|
||||
deserializer.read(customType.someMap);
|
||||
deserializer.read(customType.someHash);
|
||||
deserializer.read(customType.someSet);
|
||||
deserializer.read(customType.someMultiset);
|
||||
deserializer.read(customType.someUnorderedSet);
|
||||
deserializer.read(customType.someUnorderedMultiset);
|
||||
deserializer.read(customType.someEnum);
|
||||
deserializer.read(customType.someEnumClass);
|
||||
deserializer.read(customType.timeSpan);
|
||||
deserializer.read(customType.dateTime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
serializer.write(customType.text);
|
||||
serializer.write(customType.boolean);
|
||||
serializer.write(customType.someMap);
|
||||
serializer.write(customType.someHash);
|
||||
serializer.write(customType.someSet);
|
||||
serializer.write(customType.someMultiset);
|
||||
serializer.write(customType.someUnorderedSet);
|
||||
serializer.write(customType.someUnorderedMultiset);
|
||||
serializer.write(customType.someEnum);
|
||||
serializer.write(customType.someEnumClass);
|
||||
serializer.write(customType.timeSpan);
|
||||
serializer.write(customType.dateTime);
|
||||
}
|
||||
|
||||
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, 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
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
/// \endcond
|
||||
|
||||
/*!
|
||||
* \brief The BinaryReflectorTests class tests the (de)serializer.
|
||||
* \remarks In these tests, the required reflection code is provided by hand so the generator isn't involved yet.
|
||||
*/
|
||||
class BinaryReflectorTests : public TestFixture {
|
||||
CPPUNIT_TEST_SUITE(BinaryReflectorTests);
|
||||
CPPUNIT_TEST(testSerializeSimpleStruct);
|
||||
CPPUNIT_TEST(testDeserializeSimpleStruct);
|
||||
CPPUNIT_TEST(testSerializeNestedStruct);
|
||||
CPPUNIT_TEST(testDeserializeNestedStruct);
|
||||
CPPUNIT_TEST(testSmallSharedPointer);
|
||||
CPPUNIT_TEST(testBigSharedPointer);
|
||||
CPPUNIT_TEST(testVariant);
|
||||
CPPUNIT_TEST(testOptional);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
BinaryReflectorTests();
|
||||
|
||||
void setUp() override;
|
||||
void tearDown() override;
|
||||
|
||||
void testSerializeSimpleStruct();
|
||||
void testDeserializeSimpleStruct();
|
||||
void testSerializeNestedStruct();
|
||||
void testDeserializeNestedStruct();
|
||||
void assertTestObject(const TestObjectBinary &deserialized);
|
||||
void testSharedPointer(std::uintptr_t fakePointer);
|
||||
void testSmallSharedPointer();
|
||||
void testBigSharedPointer();
|
||||
void testVariant();
|
||||
void testOptional();
|
||||
|
||||
private:
|
||||
vector<unsigned char> m_buffer;
|
||||
TestObjectBinary m_testObj;
|
||||
NestingArrayBinary m_nestedTestObj;
|
||||
vector<unsigned char> m_expectedTestObj;
|
||||
vector<unsigned char> m_expectedNestedTestObj;
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(BinaryReflectorTests);
|
||||
|
||||
// clang-format off
|
||||
BinaryReflectorTests::BinaryReflectorTests()
|
||||
: m_buffer()
|
||||
, m_testObj()
|
||||
, m_nestedTestObj()
|
||||
, m_expectedTestObj({
|
||||
0x00, 0x00, 0x00, 0x05,
|
||||
0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x85,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x03,
|
||||
0x00, 0x00, 0x00, 0x04,
|
||||
0x00, 0x00, 0x00, 0x05,
|
||||
0x89,
|
||||
0x73, 0x6F, 0x6D, 0x65, 0x20, 0x74, 0x65, 0x78, 0x74,
|
||||
0x01,
|
||||
0x82,
|
||||
0x83, 0x62, 0x61, 0x72, 0x00, 0x00, 0x00, 0x13,
|
||||
0x83, 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x11,
|
||||
0x80,
|
||||
0x83,
|
||||
0x81, 0x31,
|
||||
0x81, 0x32,
|
||||
0x81, 0x33,
|
||||
0x84,
|
||||
0x81, 0x31,
|
||||
0x81, 0x32,
|
||||
0x81, 0x32,
|
||||
0x81, 0x33,
|
||||
0x80,
|
||||
0x80,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAB, 0xCD,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF, 0xAB,
|
||||
})
|
||||
, m_expectedNestedTestObj({
|
||||
0x93, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6e, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67,
|
||||
0x82,
|
||||
})
|
||||
{
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
void BinaryReflectorTests::setUp()
|
||||
{
|
||||
m_testObj.number = 5;
|
||||
m_testObj.number2 = 2.5;
|
||||
m_testObj.numbers = { 1, 2, 3, 4, 5 };
|
||||
m_testObj.text = "some text";
|
||||
m_testObj.boolean = true;
|
||||
m_testObj.someMap = {
|
||||
{ "foo", 17 },
|
||||
{ "bar", 19 },
|
||||
};
|
||||
m_testObj.someSet = { "1", "2", "3", "2" };
|
||||
m_testObj.someMultiset = { "1", "2", "3", "2" };
|
||||
m_testObj.someEnum = SomeEnumItem2;
|
||||
m_testObj.someEnumClass = SomeEnumClassBinary::Item3;
|
||||
m_testObj.timeSpan = TimeSpan(0xABCD);
|
||||
m_testObj.dateTime = DateTime(0xEFAB);
|
||||
m_nestedTestObj.name = "struct with nesting";
|
||||
m_expectedNestedTestObj.reserve(m_expectedNestedTestObj.size() + 2 * m_expectedTestObj.size());
|
||||
m_expectedNestedTestObj.insert(m_expectedNestedTestObj.end(), m_expectedTestObj.cbegin(), m_expectedTestObj.cend());
|
||||
m_expectedNestedTestObj.insert(m_expectedNestedTestObj.end(), m_expectedTestObj.cbegin(), m_expectedTestObj.cend());
|
||||
m_nestedTestObj.testObjects.insert(m_nestedTestObj.testObjects.end(), 2, m_testObj);
|
||||
}
|
||||
|
||||
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());
|
||||
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);
|
||||
}
|
||||
|
||||
void BinaryReflectorTests::testDeserializeSimpleStruct()
|
||||
{
|
||||
stringstream stream(ios_base::in | ios_base::binary);
|
||||
stream.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
writeBuffer(stream, m_expectedTestObj.data(), m_expectedTestObj.size());
|
||||
const auto deserialized(TestObjectBinary::fromBinary(stream));
|
||||
assertTestObject(deserialized);
|
||||
}
|
||||
|
||||
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());
|
||||
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);
|
||||
}
|
||||
|
||||
void BinaryReflectorTests::testDeserializeNestedStruct()
|
||||
{
|
||||
stringstream stream(ios_base::in | ios_base::binary);
|
||||
stream.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
writeBuffer(stream, m_expectedNestedTestObj.data(), m_expectedNestedTestObj.size());
|
||||
|
||||
const auto deserialized(NestingArrayBinary::fromBinary(stream));
|
||||
CPPUNIT_ASSERT_EQUAL(m_nestedTestObj.name, deserialized.name);
|
||||
for (const auto &testObj : deserialized.testObjects) {
|
||||
assertTestObject(testObj);
|
||||
}
|
||||
}
|
||||
|
||||
void BinaryReflectorTests::assertTestObject(const TestObjectBinary &deserialized)
|
||||
{
|
||||
CPPUNIT_ASSERT_EQUAL(m_testObj.number, deserialized.number);
|
||||
CPPUNIT_ASSERT_EQUAL(m_testObj.number2, deserialized.number2);
|
||||
CPPUNIT_ASSERT_EQUAL(m_testObj.numbers, deserialized.numbers);
|
||||
CPPUNIT_ASSERT_EQUAL(m_testObj.text, deserialized.text);
|
||||
CPPUNIT_ASSERT_EQUAL(m_testObj.boolean, deserialized.boolean);
|
||||
CPPUNIT_ASSERT_EQUAL(m_testObj.someMap, deserialized.someMap);
|
||||
CPPUNIT_ASSERT_EQUAL(m_testObj.someHash, deserialized.someHash);
|
||||
CPPUNIT_ASSERT_EQUAL(m_testObj.someSet, deserialized.someSet);
|
||||
CPPUNIT_ASSERT_EQUAL(m_testObj.someMultiset, deserialized.someMultiset);
|
||||
CPPUNIT_ASSERT_EQUAL(m_testObj.someUnorderedSet, deserialized.someUnorderedSet);
|
||||
CPPUNIT_ASSERT_EQUAL(m_testObj.someUnorderedMultiset, deserialized.someUnorderedMultiset);
|
||||
}
|
||||
|
||||
void BinaryReflectorTests::testSharedPointer(uintptr_t fakePointer)
|
||||
{
|
||||
// create a shared pointer for the fake pointer ensuring that it is not actually deleted
|
||||
shared_ptr<int> sharedPointer(reinterpret_cast<int *>(fakePointer), [](int *) {});
|
||||
|
||||
// setup stream
|
||||
stringstream stream(ios_base::in | ios_base::out | ios_base::binary);
|
||||
stream.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
|
||||
// serialize the shared pointer assuming its contents have been written before (to prevent actually dereferencing it)
|
||||
BinaryReflector::BinarySerializer serializer(&stream);
|
||||
serializer.m_pointer[fakePointer] = true;
|
||||
serializer.write(sharedPointer);
|
||||
|
||||
// deserialize the shared pointer assuming it has already been read and the type does not match
|
||||
BinaryReflector::BinaryDeserializer deserializer(&stream);
|
||||
shared_ptr<int> readPtr;
|
||||
deserializer.m_pointer[fakePointer] = "foo";
|
||||
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
|
||||
stream.seekg(0);
|
||||
deserializer.m_pointer[fakePointer] = make_shared<int>(42);
|
||||
deserializer.read(readPtr);
|
||||
CPPUNIT_ASSERT(readPtr != nullptr);
|
||||
CPPUNIT_ASSERT_EQUAL(42, *readPtr);
|
||||
}
|
||||
|
||||
void BinaryReflectorTests::testSmallSharedPointer()
|
||||
{
|
||||
testSharedPointer(std::numeric_limits<std::uintptr_t>::min() + 1);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
#include <c++utilities/tests/cppunit.h>
|
|
@ -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,42 +24,13 @@ 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;
|
||||
|
||||
// test traits
|
||||
static_assert(JsonReflector::IsArray<vector<int>>::value, "vector mapped to array");
|
||||
static_assert(JsonReflector::IsArray<list<int>>::value, "list mapped to array");
|
||||
static_assert(!JsonReflector::IsArray<string>::value, "string mapped to string");
|
||||
static_assert(JsonReflector::IsMapOrHash<map<string, int>>::value, "map mapped to object");
|
||||
static_assert(JsonReflector::IsMapOrHash<unordered_map<string, int>>::value, "hash mapped to object");
|
||||
static_assert(!JsonReflector::IsMapOrHash<vector<int>>::value, "vector not mapped to object");
|
||||
|
||||
/// \cond
|
||||
|
||||
// define some structs for testing serialization
|
||||
struct TestObject : public JsonSerializable<TestObject> {
|
||||
int number;
|
||||
double number2;
|
||||
vector<int> numbers;
|
||||
string text;
|
||||
bool boolean;
|
||||
map<string, int> someMap;
|
||||
unordered_map<string, bool> someHash;
|
||||
};
|
||||
|
||||
struct NestingObject : public JsonSerializable<NestingObject> {
|
||||
string name;
|
||||
TestObject testObj;
|
||||
};
|
||||
|
||||
struct NestingArray : public JsonSerializable<NestingArray> {
|
||||
string name;
|
||||
vector<TestObject> testObjects;
|
||||
};
|
||||
// define some enums and structs for testing serialization
|
||||
|
||||
enum SomeEnum {
|
||||
SomeEnumItem1,
|
||||
|
@ -73,6 +44,35 @@ enum class SomeEnumClass {
|
|||
Item3,
|
||||
};
|
||||
|
||||
struct TestObject : public JsonSerializable<TestObject> {
|
||||
int number;
|
||||
double number2;
|
||||
vector<int> numbers;
|
||||
string text;
|
||||
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> {
|
||||
string name;
|
||||
TestObject testObj;
|
||||
};
|
||||
|
||||
struct NestingArray : public JsonSerializable<NestingArray> {
|
||||
string name;
|
||||
vector<TestObject> testObjects;
|
||||
};
|
||||
|
||||
// pretend serialization code for structs has been generated
|
||||
namespace ReflectiveRapidJSON {
|
||||
namespace JsonReflector {
|
||||
|
@ -86,6 +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)
|
||||
|
@ -103,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";
|
||||
|
@ -115,6 +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;
|
||||
}
|
||||
|
@ -123,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";
|
||||
|
@ -138,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";
|
||||
|
@ -168,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();
|
||||
|
@ -187,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();
|
||||
|
||||
|
@ -219,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
|
||||
|
@ -258,8 +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}}"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()));
|
||||
}
|
||||
|
||||
|
@ -277,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\":{}}}"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;
|
||||
|
@ -286,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\":{}},{\"number\":43,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false,\"someMap\":{},\"someHash\":{}}]}"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\":{}}]"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()));
|
||||
}
|
||||
|
||||
|
@ -320,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\":{}}]"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()));
|
||||
}
|
||||
|
||||
|
@ -348,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\":{}}]"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.
|
||||
*/
|
||||
|
@ -413,8 +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}}"));
|
||||
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);
|
||||
|
@ -425,6 +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));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -455,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>>(
|
||||
|
@ -472,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);
|
||||
|
@ -516,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().
|
||||
*/
|
||||
|
@ -532,7 +622,7 @@ void JsonReflectorTests::testHandlingParseError()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests whether JsonDeserializationError is thrown on type mismatch.
|
||||
* \brief Tests whether errors are added on type mismatch and in other cases.
|
||||
*/
|
||||
void JsonReflectorTests::testHandlingTypeMismatch()
|
||||
{
|
||||
|
@ -544,14 +634,25 @@ void JsonReflectorTests::testHandlingTypeMismatch()
|
|||
CPPUNIT_ASSERT_EQUAL(0_st, errors.size());
|
||||
|
||||
NestingObject::fromJson("{\"name\":\"nesting\",\"testObj\":{\"number\":\"42\",\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":"
|
||||
"\"test\",\"boolean\":false}}",
|
||||
"\"test\",\"boolean\":false,\"someSet\":[\"a\",\"a\"],\"someMultiset\":[\"a\",\"a\"],\"someUnorderedSet\":[\"a\",\"a\"],"
|
||||
"\"someUnorderedMultiset\":[\"a\",\"a\"]}}",
|
||||
&errors);
|
||||
CPPUNIT_ASSERT_EQUAL(1_st, errors.size());
|
||||
CPPUNIT_ASSERT_EQUAL(3_st, errors.size());
|
||||
CPPUNIT_ASSERT_EQUAL(JsonDeserializationErrorKind::TypeMismatch, errors.front().kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JsonType::Number, errors.front().expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JsonType::String, errors.front().actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("number"s, string(errors.front().member));
|
||||
CPPUNIT_ASSERT_EQUAL("TestObject"s, string(errors.front().record));
|
||||
CPPUNIT_ASSERT_EQUAL(JsonDeserializationErrorKind::UnexpectedDuplicate, errors[1].kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JsonType::Array, errors[1].expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JsonType::Array, errors[1].actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("someSet"s, string(errors[1].member));
|
||||
CPPUNIT_ASSERT_EQUAL("TestObject"s, string(errors[1].record));
|
||||
CPPUNIT_ASSERT_EQUAL(JsonDeserializationErrorKind::UnexpectedDuplicate, errors[2].kind);
|
||||
CPPUNIT_ASSERT_EQUAL(JsonType::Array, errors[2].expectedType);
|
||||
CPPUNIT_ASSERT_EQUAL(JsonType::Array, errors[2].actualType);
|
||||
CPPUNIT_ASSERT_EQUAL("someUnorderedSet"s, string(errors[2].member));
|
||||
CPPUNIT_ASSERT_EQUAL("TestObject"s, string(errors[2].record));
|
||||
errors.clear();
|
||||
|
||||
NestingObject::fromJson("{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":1,\"text\":"
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
#include "../traits.h"
|
||||
#include "../versioning.h"
|
||||
|
||||
#include "../binary/serializable.h"
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
// 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);
|
||||
REFLECTIVE_RAPIDJSON_TREAT_AS_SET(Bar);
|
||||
REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_SET(Foo);
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
using namespace std;
|
||||
using namespace ReflectiveRapidJSON;
|
||||
|
||||
// test traits
|
||||
static_assert(IsArray<vector<int>>::value, "vector mapped to array");
|
||||
static_assert(IsArray<list<int>>::value, "list mapped to array");
|
||||
static_assert(!IsArray<set<int>>::value, "set not considered an array");
|
||||
static_assert(!IsArray<multiset<int>>::value, "multiset not considered an array");
|
||||
static_assert(IsArrayOrSet<set<int>>::value, "set is array or set");
|
||||
static_assert(IsArrayOrSet<multiset<int>>::value, "multiset is array or set");
|
||||
static_assert(IsArrayOrSet<Foo>::value, "Foo is array or set via TreatAsMultiSet");
|
||||
static_assert(IsArrayOrSet<Bar>::value, "Foo is array or set via TreatAsSet");
|
||||
static_assert(!IsArrayOrSet<string>::value, "string not mapped to array or set though it is iteratable");
|
||||
static_assert(IsSet<set<int>>::value, "set mapped to set");
|
||||
static_assert(IsSet<unordered_set<int>>::value, "unordered_set mapped to set");
|
||||
static_assert(IsSet<Bar>::value, "Bar mapped to set via TreatAsSet");
|
||||
static_assert(!IsSet<string>::value, "string not mapped to set");
|
||||
static_assert(IsMultiSet<unordered_multiset<int>>::value, "multiset");
|
||||
static_assert(IsMultiSet<Foo>::value, "Foo mapped to multiset via TreatAsMultiSet");
|
||||
static_assert(!IsMultiSet<string>::value, "string not mapped to multiset");
|
||||
static_assert(!IsArray<string>::value, "string not mapped to array though it is iteratable");
|
||||
static_assert(IsMapOrHash<map<string, int>>::value, "map mapped to object");
|
||||
static_assert(IsMapOrHash<unordered_map<string, int>>::value, "hash mapped to object");
|
||||
static_assert(!IsMapOrHash<vector<int>>::value, "vector not mapped to object");
|
||||
static_assert(IsMapOrHash<Foo>::value, "Foo mapped to object via TreatAsMapOrHash");
|
||||
static_assert(IsMultiMapOrHash<multimap<string, int>>::value, "multimap mapped to object");
|
||||
static_assert(IsMultiMapOrHash<unordered_multimap<string, int>>::value, "unordered multimap mapped to object");
|
||||
static_assert(!IsMultiMapOrHash<vector<int>>::value, "vector not mapped to object");
|
||||
static_assert(IsMultiMapOrHash<Foo>::value, "Foo mapped to object via TreatAsMultiMapOrHash");
|
||||
static_assert(IsIteratableExceptString<std::vector<int>>::value, "vector is iteratable");
|
||||
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");
|
|
@ -0,0 +1,63 @@
|
|||
#ifndef REFLECTIVE_RAPIDJSON_TRAITS
|
||||
#define REFLECTIVE_RAPIDJSON_TRAITS
|
||||
|
||||
#include <c++utilities/misc/traits.h>
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#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> {};
|
||||
/// \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> {};
|
||||
/// \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> {};
|
||||
/// \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> {};
|
||||
|
||||
#define REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH(T) \
|
||||
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> {}
|
||||
#define REFLECTIVE_RAPIDJSON_TREAT_AS_SET(T) \
|
||||
template <> struct TreatAsSet<T> : public Traits::Bool<true> {}
|
||||
#define REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_SET(T) \
|
||||
template <> struct TreatAsMultiSet<T> : public Traits::Bool<true> {}
|
||||
|
||||
// define traits to check for arrays, sets and maps
|
||||
template <typename Type>
|
||||
using IsMapOrHash
|
||||
= Traits::Any<Traits::IsSpecializationOf<Type, std::map>, Traits::IsSpecializationOf<Type, std::unordered_map>, TreatAsMapOrHash<Type>>;
|
||||
template <typename Type>
|
||||
using IsMultiMapOrHash = Traits::Any<Traits::IsSpecializationOf<Type, std::multimap>, Traits::IsSpecializationOf<Type, std::unordered_multimap>,
|
||||
TreatAsMultiMapOrHash<Type>>;
|
||||
template <typename Type>
|
||||
using IsSet = Traits::Any<Traits::IsSpecializationOf<Type, std::set>, Traits::IsSpecializationOf<Type, std::unordered_set>, TreatAsSet<Type>>;
|
||||
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<Traits::IsSpecializationOf<Type, std::basic_string_view>>, Traits::Not<IsMapOrHash<Type>>, Traits::Not<IsMultiMapOrHash<Type>>>,
|
||||
TreatAsSet<Type>, TreatAsMultiSet<Type>>;
|
||||
template <typename Type>
|
||||
using IsArray = Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>,
|
||||
Traits::Not<IsMapOrHash<Type>>, Traits::Not<IsMultiMapOrHash<Type>>, Traits::Not<IsSet<Type>>, Traits::Not<IsMultiSet<Type>>>;
|
||||
template <typename Type>
|
||||
using IsIteratableExceptString = Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>,
|
||||
Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>;
|
||||
template <typename Type> using IsVariant = Traits::All<Traits::IsSpecializationOf<Type, std::variant>>;
|
||||
|
||||
} // namespace ReflectiveRapidJSON
|
||||
|
||||
#endif // REFLECTIVE_RAPIDJSON_TRAITS
|
|
@ -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