Compare commits

...

133 Commits

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

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

It should be fine because if the base class is versioned it won't make a
difference but if it is versioned the version from the derived class can be
utilized.
2022-05-15 21:00:02 +02:00
Martchus 9c3bf01c8f Avoid warnings about unused var `allocator` for empty classes 2022-05-15 20:52:30 +02:00
Martchus 1e3417f8d0 Fix (de)serialization of std::int8_t/std::uint8_t
Those types are not supported by RapidJSON and therefore need extra
handling.
2022-05-15 03:11:39 +02:00
Martchus a6b9d771aa Do not treat warning "'this' pointer is null" appearing with GCC 12 as error
It is about code included from libclang and likely wrong so let's just not
abort the compilation due to it.
2022-05-12 20:23:33 +02:00
Martchus 762540f5e5 Add support for `std::optional` 2022-05-07 18:40:37 +02:00
Martchus 0633923935 Simplify use of type traits in `json/reflector.h` 2022-05-07 16:52:32 +02:00
Martchus 11491b1387 Add stalebot config 2022-04-12 01:05:03 +02:00
Martchus df787f3105 Add copyright notice 2022-04-05 20:23:32 +02:00
Martchus 2cc044c705 Adapt tests to efaa8a8 2022-03-28 17:06:28 +02:00
Martchus 4966625d8b Clarify that license is "GPL-2-or-later" 2022-03-15 21:46:56 +01:00
Martchus efaa8a8441 Avoid warnings about using uninitialized variable 2022-02-22 20:03:49 +01:00
Martchus 22611457f9 Fix plural of pointer in README.md 2022-02-05 22:28:20 +01:00
Martchus 59ff3c19eb Allow reporting a type mismatch specifying the expected RapidJSON type directly 2022-01-03 23:24:40 +01:00
Martchus f8f551a78a Fix include and namespace-prefix for using `std::move` in JSON reflector 2021-12-05 23:34:39 +01:00
Martchus 8b66ca3e6b Avoid using `DeclBase::getLangOpts()` to support older Clang versions
The `DeclBase::getLangOpts()` is merely a convenience but only available
in Clang 11 or newer.
2021-09-19 01:17:30 +02:00
Martchus 44c6b8c609 Throw exception during binary deserialization when version is not supported 2021-07-25 19:19:02 +02:00
Martchus 13428667f8 Remove surplus space 2021-07-25 19:13:29 +02:00
Martchus 852dfb7e3c Add symlink for versioning 2021-07-25 17:31:07 +02:00
Martchus 0a902ac30c Add experimental versioning for binary (de)serializer 2021-07-13 00:37:05 +02:00
Martchus 5e72012ed5 Fix typos found via `codespell --skip .git -w` 2021-07-03 19:50:25 +02:00
Martchus a4dd52acfa Define REFLECTIVE_RAPIDJSON_GENERATOR macro during generator runs
So one can distinguish regular compilation from the generator run by
checking this macro.
2021-05-16 19:25:36 +02:00
Martchus e3d32ddfa1 Use std::string_view for CodeFactory parameters (where possible) 2021-05-16 19:25:30 +02:00
Martchus 8f1909dfdf Exit when parsing arguments fails 2021-05-13 16:56:27 +02:00
Martchus 874c964e0b Prevent warnings about unused variables in generated code 2021-03-22 14:16:40 +01:00
Martchus 30735ba187 Fix warnings 2021-03-20 21:25:56 +01:00
Martchus 5110cff5eb Fix documentation for std::string_view's push/toJsonDocument function 2021-01-15 17:48:46 +01:00
Martchus dc7c74c497 Update minimum C++ version mentioned in README
This likely doesn't work anymore with C++14 and I won't put any effort into
C++14 compatibility anymore at this point.
2021-01-15 15:27:28 +01:00
Martchus 80183f5269 Remove list of supported versions as it is too much work to maintain 2021-01-15 15:24:50 +01:00
Martchus 5c49a438ad Use header-only target of c++utilities
The current approach even distinguished between the build and install
interface. However, CPP_UTILITIES_INCLUDE_DIRS is not correctly evaluated
at the time PUBLIC_INCLUDE_DIRS is populated here because the variable
PACKAGE_PREFIX_DIR from the c++utilities config module still points to the
build directory at build time and using $<INSTALL_INTERFACE:…> can not help
with that.
2021-01-01 18:48:14 +01:00
Martchus 6252a7335a Apply cmake-format and clang-format 2020-12-05 21:29:55 +01:00
Martchus 2b6634d574 Allow supplying RapidJSON from outer scope 2020-12-05 21:29:38 +01:00
Martchus 0010e32515 Improve download section of README 2020-12-05 21:28:22 +01:00
Martchus 1970145b90 Add documentation regarding direct RapidJSON usage 2020-06-29 20:59:01 +02:00
Martchus 1e95c3d1ca Don't use StringRef with RapidJSON's SetString and allocator
There is no overload taking a RAPIDJSON_NAMESPACE::StringRef and an
allocator. So the StringRef will be implicitly converted back to a plain
CharType pointer causing an additional length calculation.
2020-06-29 20:26:19 +02:00
Martchus c33e1687cb Support serializing std::string_view 2020-06-26 22:08:05 +02:00
Martchus 4ff49156da Apply clang-format 2020-05-28 12:02:22 +02:00
Martchus 18716b17be
Merge pull request #3 from otreblan/master
Fix missing header in gcc10
2020-05-28 11:51:42 +02:00
otreblan beb4491b18
Fix missing header in gcc10 2020-05-27 22:20:22 -05:00
Martchus 8c032ee7a6 Fix compatibility with LLVM/Clang 10 2020-04-12 17:22:37 +02:00
Martchus c4f622df8d Apply cmake-format 2020-03-08 13:52:01 +01:00
Martchus 442359f5bf Add a function to format JSON deserialization errors 2020-02-29 22:35:02 +01:00
Martchus 86481fa459 Prevent CMake error when using CMake < 3.15.0 2020-02-18 19:40:48 +01:00
Martchus cf41f4596a Prevent passing "empty" flags to the code generator
Otherwise flags like "-D -I/foo" might be passed to clang which
will then complain that "-I/foo" is not a valid name for a macro.
2020-02-14 16:26:50 +01:00
Martchus 4b4d674f56 Allow adding clang options from dependencies 2020-01-31 20:43:01 +01:00
Martchus 891b96a38f Fix build with custom target suffix 2020-01-31 19:49:18 +01:00
Martchus c68d9ea384 Format CMake files with cmake-format 0.6.7 2020-01-26 20:42:03 +01:00
Martchus e7bbdd0af6 Allow logging the exact list of options passed to clang
Since the options get splitted and not just passed as-is
this can be useful for debugging.
2020-01-26 20:41:34 +01:00
Martchus ed3f89953f Add missing symlinks to include directory 2020-01-26 20:38:47 +01:00
Martchus 6e4077eed2 Add JsonDeserializationErrors::ThrowOn::All 2020-01-26 20:38:24 +01:00
Martchus 2f1098ae3d Make m_pointer of BinarySerializer and BinaryDeserializer private 2020-01-12 00:50:30 +01:00
Martchus b37f467dcd Fix name of CMake package in example 2020-01-12 00:33:02 +01:00
Martchus 7482c64931 Support --binary-visibility in CMake function 2020-01-11 14:54:14 +01:00
Martchus f9fc9e02b7 Fix URL in project meta-data 2020-01-02 20:31:09 +01:00
Martchus 72a11c22b1 Apply cmake-format 0.6.5 2019-12-27 01:43:41 +01:00
Martchus 48639b42b2 Support serialization of std::pair 2019-12-27 01:43:28 +01:00
Martchus 717fb2f037 Polish lib/binary/reflector.h 2019-12-27 01:42:34 +01:00
Martchus 450588af89 Support multimap and unordered_multimap
The previously (undocumented) implementation used non-unique
keys in JSON objects. This is not strictly forbidden by the
RFC but not recommended. Multiple values are now stored within
an array instead.
2019-12-27 01:40:34 +01:00
Martchus 022a174028 Remove wrong quote in README.md 2019-12-06 16:52:10 +01:00
Martchus 1192c2d74a Apply cmake-format 0.6.2 2019-12-06 16:51:47 +01:00
Martchus 03563aafbf Fix generator tests; it needs C++17 2019-11-14 18:13:39 +01:00
Martchus f75c8d77a2 Fix finding LibTooling when 'clang-cpp' target is used
See https://releases.llvm.org/9.0.0/tools/clang/docs/ReleaseNotes.html#build-system-changes
2019-11-14 17:18:49 +01:00
Martchus 456702e009 Remove unused std::monostate handling
This wasn't useful after all.
2019-11-03 22:38:27 +01:00
Martchus 3374e4ea6c Fix passing C++ version in CMake macro 2019-11-03 22:37:55 +01:00
Martchus e29db0fa87 Prevent warnings about unused lambda captures 2019-11-03 22:37:19 +01:00
Martchus 1024b8e391 Use override in test fixtures 2019-11-03 22:33:29 +01:00
Martchus c3dc381425 Support `std::variant` 2019-11-03 22:02:56 +01:00
Martchus dd652b4de7 Apply CMake format 2019-10-12 19:04:17 +02:00
Martchus 7e5a32265d Add CLI help and completion for binary generator 2019-10-05 01:36:21 +02:00
Martchus 00dd569869 Update/improve README.md 2019-10-05 01:31:38 +02:00
Martchus d8e626d259 Fix toJsonDocument() for maps 2019-10-05 01:05:48 +02:00
Martchus d7e7bdb703 Add std::optional to TODOs 2019-08-09 17:59:00 +02:00
Martchus fecde7d2d1 Improve adding RapidJSON include dirs to tests
* Also check for `RapidJSON_INCLUDE_DIRS` which seems to be used now
* Do not add `-I` without subsequent path if the include directory
  is missing/empty which would lead to the Clang error:
  `error: unable to handle compilation, expected exactly one compiler job in ''`
2019-08-09 17:54:08 +02:00
Martchus f21f255e94 Don't use the same enum names twice
Prevents

```
[  168s] /home/abuild/rpmbuild/BUILD/reflective-rapidjson-1563638877.5f7c18b/lib/tests/jsonreflector.cpp:41:12: warning: type 'SomeEnumClass' violates the C++ One Definition Rule [-Wodr]
[  168s]    41 | enum class SomeEnumClass {
[  168s]       |            ^
[  168s] /home/abuild/rpmbuild/BUILD/reflective-rapidjson-1563638877.5f7c18b/lib/tests/binaryreflector.cpp:39:12: note: a type with different precision is defined in another translation unit
[  168s]    39 | enum class SomeEnumClass : std::uint16_t {
[  168s]       |            ^
```

observed in LTO-enabled builds
2019-08-09 17:48:08 +02:00
Martchus 8394c145f6 Update version 2019-08-05 20:27:21 +02:00
Martchus 5f7c18b59e Set project() on top-level
See https://github.com/Martchus/cpp-utilities/pull/15
2019-07-20 18:07:57 +02:00
Martchus 8f06bf0272 Do not call vector::reserve() in loop 2019-06-20 22:54:17 +02:00
Martchus a2b40753c3 Adapt adding include dirs to c++utilities 5 2019-06-20 22:53:03 +02:00
Martchus 6542eab80d Adapt to changes in c++utilities 2019-06-14 19:16:51 +02:00
Martchus 8cac133104 Adapt to changes in c++utilities 2019-06-10 22:46:06 +02:00
Martchus 2194000938 Apply cmake-format 0.5.1 2019-06-01 12:16:27 +02:00
Martchus dd174920fe Adapt to c++utilities v5 2019-05-04 16:09:58 +02:00
Martchus 9b80d662bb Catch polymorphic type by reference 2019-04-19 22:24:32 +02:00
Martchus 669aa16479 Fix building against LLVM/Clang 8 2019-03-27 14:41:57 +01:00
Martchus 2b0acbdd1e Cast pointer to the right type on non 64-bit architectures
That could explain why the tests sometimes fail under armv7l (before
making the fix for big pointer IDs).
2019-03-08 17:44:20 +01:00
Martchus 0bf19df0d9 Add explicit test for type mismatch on binary serialization 2019-03-08 17:40:36 +01:00
Martchus 04852627b2 Fix binary serializiation of shared_ptr too big for variable length encoding
* Retain backward compatibility by using either variable length int
  or regular 64-bit int extending the flags
* Add test for both cases
2019-03-08 17:40:08 +01:00
Martchus b058e9e9b9 Fix include for "empty" case in generator tests 2019-02-13 11:39:09 +01:00
Martchus 1f6fade6c2 Apply cmake-format 2019-02-09 21:25:11 +01:00
Martchus 39dcba215e Let CMake generate code for test application 2019-01-13 22:27:35 +01:00
Martchus 974c0b0396 Allow to obtain only the JSON document
So the final serialization can be customized
2018-11-11 22:55:59 +01:00
Martchus ef27d71f43 Fix multiple definition error caused by forward declarations 2018-11-08 00:38:45 +01:00
Martchus 712eb4fb28 Fix compiling with GCC
Template specializations produced by the macros apparently
must be in the same namespace for GCC (not sure whether this
is actually enforced by the standard).
2018-11-01 04:54:46 +00:00
Martchus 7e649fe2bf Require c++utilities 4.16.0 2018-11-01 04:54:46 +00:00
Martchus d64174c000 Add experimental support for multimap
Makes sense for binary (de)serialization, not sure about
JSON (de)serialization
2018-11-01 04:54:46 +00:00
Martchus f77229471a Allow to teat custom types as std::map/std::set 2018-11-01 04:54:46 +00:00
Martchus 98b25c813e Update README.md 2018-11-01 04:54:33 +00:00
Martchus 40b85b411e Bump version 0.0.5 => 0.0.6 2018-11-01 04:54:24 +00:00
Martchus aa92cab4e5 Test pointer handling of binary serialization 2018-10-28 21:35:32 +01:00
Martchus 5835cd85a5 Remove unused createFileIDForMemBuffer() 2018-09-04 19:39:32 +02:00
Martchus 933d14ef1d Check whether include dirs for generator exist at configure time 2018-08-04 15:22:32 +02:00
Martchus 29cd810507 Use C++ standard from target in CMake macro 2018-07-27 17:40:15 +02:00
Martchus 7fcbead8e3 Escape angle brackets in README 2018-07-22 20:51:54 +02:00
Martchus 25e7891573 Apply clang-format 2018-06-26 00:10:44 +02:00
Martchus da56134456 binary (de)serialization: Prevent duplicating shared pointer 2018-06-26 00:10:09 +02:00
Martchus f140c7f436 Use C++ 17 2018-06-26 00:07:59 +02:00
Martchus c170993392 Reduce code duplication between serialization generators 2018-06-23 18:04:30 +02:00
Martchus e93be04e35 Implement generator for binary (de)serialization
Still need to refactor common code with the JSON generator.
2018-06-23 17:25:30 +02:00
Martchus 6117ef3e1d Rename functions of BinarySerializable
So the names are more like the ones in JsonSerializable.
2018-06-23 17:24:28 +02:00
Martchus 04dccdbd74 Make binary (de)serialization for chrono utils inline 2018-06-23 17:23:37 +02:00
Martchus ac1fe81497 Add Boost.Hana support for binary (de)serialization 2018-06-23 15:30:51 +02:00
Martchus 9dc7bd371c Apply clang-format 2018-06-23 14:35:53 +02:00
Martchus 316c1ba838 Add library for basic binary (de)serialization 2018-06-23 14:35:43 +02:00
Martchus 551ead193e Update patch version to 0.0.5 2018-05-08 18:53:21 +02:00
Martchus 8628427e6d Fix usage of enable_if with GCC 8
Seems like the trick with the three dots isn't working
with GCC 8 anymore. So let's make it a default template
parameter then.

Not sure whether GCC 8 is here correct and whether this
workaround causes further trouble.
2018-05-07 23:48:37 +02:00
Martchus 36463cd6dc Apply clang-format from LLVM 6 2018-04-04 20:31:49 +02:00
Martchus fa6e16b45d Update README section about packages 2018-04-04 20:30:40 +02:00
Martchus 7db20f5ad5 List all required LLVM/Clang libraries explicitely
Those libraries are direct dependencies and must be listed
explicitely when linking dynamically because transitive
dependencies of clangTooling are not pulled automatically
by CMake in this case.

This is required to use LLVM 6 which has switched to dynamic
libraries (at least under Arch Linux).
2018-03-14 18:33:15 +01:00
Martchus 03e3a4bc67 Support std::(unordered_)?(multi)?set 2018-02-26 22:39:09 +01:00
53 changed files with 3366 additions and 680 deletions

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

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

View File

@ -1,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
View File

@ -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 &lt;reflective_rapidjson/json/serializable.h&gt;
// define structures, eg.
struct TestObject : public ReflectiveRapidJSON::JsonSerializable<TestObject> {
struct TestObject : public ReflectiveRapidJSON::JsonSerializable&lt;TestObject&gt; {
int number;
double number2;
vector<int> numbers;
vector&lt;int&gt; numbers;
string text;
bool boolean;
};
struct NestingObject : public ReflectiveRapidJSON::JsonSerializable<NestingObject> {
struct NestingObject : public ReflectiveRapidJSON::JsonSerializable&lt;NestingObject&gt; {
string name;
TestObject testObj;
};
struct NestingArray : public ReflectiveRapidJSON::JsonSerializable<NestingArray> {
struct NestingArray : public ReflectiveRapidJSON::JsonSerializable&lt;NestingArray&gt; {
string name;
vector<TestObject> testObjects;
vector&lt;TestObject&gt; testObjects;
};
// serialize to JSON
NestingArray obj{ ... };
cout << "JSON: " << obj.toJson().GetString();
cout &lt;&lt; "JSON: " &lt;&lt; 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 &lt;reflective_rapidjson/binary/serializable.h&gt;
struct TestObject : public ReflectiveRapidJSON::BinarySerializable&lt;TestObject&gt;
</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 &lt;reflective_rapidjson/json/serializable-boosthana.h&gt;
// define structures using BOOST_HANA_DEFINE_STRUCT, eg.
struct TestObject : public JsonSerializable<TestObject> {
struct TestObject : public JsonSerializable&lt;TestObject&gt; {
BOOST_HANA_DEFINE_STRUCT(TestObject,
(int, number),
(double, number2),
(vector<int>, numbers),
(vector&lt;int&gt;, numbers),
(string, text),
(bool, boolean)
);
};
struct NestingObject : public JsonSerializable<NestingObject> {
struct NestingObject : public JsonSerializable&lt;NestingObject&gt; {
BOOST_HANA_DEFINE_STRUCT(NestingObject,
(string, name),
(TestObject, testObj)
);
};
struct NestingArray : public JsonSerializable<NestingArray> {
struct NestingArray : public JsonSerializable&lt;NestingArray&gt; {
BOOST_HANA_DEFINE_STRUCT(NestingArray,
(string, name),
(vector<TestObject>, testObjects)
(vector&lt;TestObject&gt;, testObjects)
);
};
// serialize to JSON
NestingArray obj{ ... };
cout << "JSON: " << obj.toJson().GetString();
cout &lt;&lt; "JSON: " &lt;&lt; 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&lt;ThridPartyStruct&gt;("...");
</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&lt;SomeStruct&gt; {
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 &lt;reflective_rapidjson/binary/serializable.h&gt;
// example struct where version is *not* serialized/deserialized; defaults to version from
// outer scope when reading/writing, defaults to version 0 on top-level
struct Nested : public BinarySerializable&lt;Nested&gt; { //
std::uint32_t foo; // will be read/written in any case
as_of_version(3):
std::uint32_t bar; // will be read/written if outer scope version is &gt;= 3
};
// example struct where version is serialized/deserialized; defaults to version 3 when writing
struct Example : public BinarySerializable&lt;Example, 3&gt; {
Nested nested; // will be read/written in any case, version is "propagated down"
std::uint32_t a, b; // will be read/written in any case
until_version(2):
std::uint32_t c, d; // will be read/written if version is &lt;= 2
as_of_version(3):
std::uint32_t e, f; // will be read/written if version is &gt;= 3
as_of_version(4):
std::uint32_t g; // will be read/written if version is &gt;= 4
};
</pre>
The version specified as template argument is also assumed to be the highest supported version.
If a higher version is encountered during deserialization, `BinaryVersionNotSupported` is thrown
and the deserialization aborted.
Note that the versioning is mostly untested at this point.
### Remarks
* Static member variables and member functions are currently ignored by the generator.
* It is currently not possible to ignore a specific member variable.
@ -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).

View File

@ -7,14 +7,14 @@
- [x] Add documentation (install instructions, usage)
- [x] Allow making 3rdparty classes/structs reflectable
- [x] Add additional parameter for code generator to allow specifying relevant classes
explicitely
explicitly
- [x] Fix traits currently relying on `JsonSerializable` being base class
- [x] Allow exporting symbols
- [x] Fix the massive number of warnings which are currently being created by the code generator (missing `-resource-dir` was the problem)
- [ ] Test with libc++ (currently only tested with libstdc++)
- [ ] Support templated classes
- [ ] Allow (de)serialization of static members (if that makes sense?)
- [ ] Allow ignoring particular members or selecting specificly which member variables should be considered
- [ ] Allow ignoring particular members or selecting specifically which member variables should be considered
* This could work similar to Qt's Signals & Slots macros.
* but there should also be a way to do this for 3rdparty types.
* Note that currently, *all* public member variables are (de)serialized.
@ -29,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)

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# metadata
set(META_PROJECT_NAME reflective_rapidjson_generator)
@ -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)

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,9 @@
#include "./codefactory.h"
#include "./clangversionabstraction.h"
#include "./frontendaction.h"
#include "resources/config.h"
#include <clang/Basic/FileManager.h>
#include <clang/Frontend/FrontendActions.h>
#include <clang/Tooling/Tooling.h>
@ -20,7 +23,7 @@ struct CodeFactory::ToolInvocation {
CodeFactory::ToolInvocation::ToolInvocation(CodeFactory &factory)
: fileManager({ "." })
, invocation(factory.makeClangArgs(), new FrontendAction(factory), &fileManager)
, invocation(factory.makeClangArgs(), maybe_unique(new FrontendAction(factory)), &fileManager)
{
fileManager.Retain();
}
@ -29,8 +32,8 @@ CodeFactory::ToolInvocation::ToolInvocation(CodeFactory &factory)
* \brief Constructs a new instance.
* \remarks The specified arguments are not copied and must remain valid for the live-time of the code factory.
*/
CodeFactory::CodeFactory(
const char *applicationPath, const std::vector<const char *> &sourceFiles, const std::vector<string> &clangOptions, std::ostream &os)
CodeFactory::CodeFactory(std::string_view applicationPath, const std::vector<const char *> &sourceFiles,
const std::vector<std::string_view> &clangOptions, std::ostream &os)
: m_applicationPath(applicationPath)
, m_sourceFiles(sourceFiles)
, m_clangOptions(clangOptions)
@ -49,8 +52,8 @@ CodeFactory::~CodeFactory()
*/
std::vector<string> CodeFactory::makeClangArgs() const
{
static const initializer_list<const char *> flags
= { m_applicationPath, "-x", "c++", "-Wno-pragma-once-outside-header", "-std=c++14", "-fsyntax-only" };
static const initializer_list<std::string_view> flags
= { m_applicationPath, "-x", "c++", "-Wno-pragma-once-outside-header", "-std=c++14", "-fsyntax-only", "-D" PROJECT_VARNAME_UPPER };
vector<string> clangArgs;
clangArgs.reserve(flags.size() + m_clangOptions.size() + m_sourceFiles.size());
clangArgs.insert(clangArgs.end(), flags.begin(), flags.end());
@ -82,7 +85,7 @@ bool CodeFactory::generate() const
}
/*!
* \brief Reads (relevent) AST elements using Clang and generates code.
* \brief Reads (relevant) AST elements using Clang and generates code.
*/
bool CodeFactory::run()
{

View File

@ -29,13 +29,13 @@ class CodeFactory {
friend class Visitor;
public:
CodeFactory(
const char *applicationPath, const std::vector<const char *> &sourceFiles, const std::vector<std::string> &clangOptions, std::ostream &os);
CodeFactory(std::string_view applicationPath, const std::vector<const char *> &sourceFiles, const std::vector<std::string_view> &clangOptions,
std::ostream &os);
~CodeFactory();
const std::vector<std::unique_ptr<CodeGenerator>> &generators() const;
template <typename GeneratorType, typename... Args> void addGenerator(Args &&... args);
template <typename GeneratorType, typename... Args> auto bindGenerator(Args &&... args);
template <typename GeneratorType, typename... Args> void addGenerator(Args &&...args);
template <typename GeneratorType, typename... Args> auto bindGenerator(Args &&...args);
bool run();
clang::CompilerInstance *compilerInstance();
@ -50,9 +50,9 @@ private:
bool generate() const;
std::vector<std::string> makeClangArgs() const;
const char *const m_applicationPath;
std::string_view m_applicationPath;
const std::vector<const char *> &m_sourceFiles;
const std::vector<std::string> &m_clangOptions;
const std::vector<std::string_view> &m_clangOptions;
std::ostream &m_os;
std::vector<std::unique_ptr<CodeGenerator>> m_generators;
std::unique_ptr<ToolInvocation> m_toolInvocation;
@ -64,7 +64,7 @@ private:
* \brief Instantiates a code generator of the specified type and adds it to the current instance.
* \remarks The specified \a args are forwarded to the generator's constructor.
*/
template <typename GeneratorType, typename... Args> void CodeFactory::addGenerator(Args &&... args)
template <typename GeneratorType, typename... Args> void CodeFactory::addGenerator(Args &&...args)
{
m_generators.emplace_back(std::make_unique<GeneratorType>(*this, std::forward<Args>(args)...));
}
@ -101,7 +101,7 @@ template <typename T> T &&wrapReferences(T &&val)
* - The specified \a args are forwarded to the generator's constructor.
* - No copy of \a args passed by reference is made.
*/
template <typename GeneratorType, typename... Args> auto CodeFactory::bindGenerator(Args &&... args)
template <typename GeneratorType, typename... Args> auto CodeFactory::bindGenerator(Args &&...args)
{
return std::bind(&CodeFactory::addGenerator<GeneratorType, Args...>, this, Detail::wrapReferences(std::forward<Args>(args)...));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +0,0 @@
#include <c++utilities/tests/cppunit.h>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

341
lib/binary/reflector.h Normal file
View File

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

65
lib/binary/serializable.h Normal file
View File

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

View File

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

View File

@ -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
/*!

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
/*!
* \file serializable.h
* \brief Contains only the definiation of the JsonSerializable template class which makes the reflection
* \brief Contains only the definition of the JsonSerializable template class which makes the reflection
* accessible. The actual implementation is found in jsonreflector.h and generated files.
*/
@ -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.

View File

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

View File

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

View File

@ -1 +0,0 @@
#include <c++utilities/tests/cppunit.h>

View File

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

View File

@ -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;
/*!

View File

@ -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\":"

69
lib/tests/traits.cpp Normal file
View File

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

63
lib/traits.h Normal file
View File

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

60
lib/versioning.h Normal file
View File

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