2017-10-28 18:24:12 +02:00
|
|
|
# Reflective RapidJSON
|
2017-10-18 23:07:17 +02:00
|
|
|
|
2017-10-24 18:00:51 +02:00
|
|
|
The main goal of this project is to provide a code generator for serializing/deserializing C++ objects to/from JSON
|
|
|
|
using Clang and RapidJSON.
|
|
|
|
|
2018-11-01 05:54:33 +01:00
|
|
|
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.
|
2018-06-23 17:25:30 +02:00
|
|
|
|
2018-11-01 05:54:33 +01:00
|
|
|
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.
|
2017-10-25 19:16:37 +02:00
|
|
|
|
2018-01-07 00:56:12 +01:00
|
|
|
## Open for other reflection approaches
|
|
|
|
The reflection implementation used behind the scenes of this library is exchangeable:
|
|
|
|
|
|
|
|
* This repository already provides a small, additional header to use RapidJSON with Boost.Hana. This allows to
|
|
|
|
serialize or dezerialize simple data structures declared using the `BOOST_HANA_DEFINE_STRUCT` macro rather than
|
|
|
|
requiring the code generator.
|
|
|
|
* When native reflection becomes standardized, it would be possible to make use of it as well. In this case,
|
|
|
|
the code generator could still act as a fallback.
|
2017-10-24 18:00:51 +02:00
|
|
|
|
2018-01-15 01:25:23 +01:00
|
|
|
## Current state
|
|
|
|
The basic functionality is implemented, tested and documented:
|
|
|
|
|
2018-02-05 21:32:05 +01:00
|
|
|
* Serialization and deserialization of datatypes listed under "Supported datatypes"
|
|
|
|
* Nesting and inheritance is possible
|
|
|
|
* Adapting 3rdparty structs/classes is supported
|
|
|
|
* Basic error handling when deserializing
|
2018-01-15 01:25:23 +01:00
|
|
|
* CMake macro to conveniently include the code generator into the build process
|
2018-02-05 21:32:05 +01:00
|
|
|
* Allow to use Boost.Hana
|
2018-01-15 01:25:23 +01:00
|
|
|
|
2018-02-05 21:32:05 +01:00
|
|
|
### Planned features and TODOs
|
|
|
|
There are still things missing which would likely be very useful in practise. The following list contains the
|
|
|
|
open TODOs which are supposed to be most relevant in practise:
|
2018-01-15 01:25:23 +01:00
|
|
|
|
2018-01-21 19:44:33 +01:00
|
|
|
* [ ] Allow to specify which member variables should be considered
|
|
|
|
* This could work similar to Qt's Signals & Slots macros.
|
2018-02-05 21:32:05 +01:00
|
|
|
* But there should also be a way to do this for 3rdparty types.
|
|
|
|
* Note that currently all public, non-static member variables are (de)serialized.
|
2018-01-15 01:25:23 +01:00
|
|
|
* [ ] Support getter/setter methods
|
2018-01-21 19:44:33 +01:00
|
|
|
* [ ] Allow to serialize the result of methods
|
|
|
|
* [ ] Allow to pass a deserialized value to a method
|
|
|
|
* [ ] Validate enum values when deserializing
|
|
|
|
* [ ] Untie serialization and deserialization
|
|
|
|
|
2018-02-05 21:32:05 +01:00
|
|
|
For a full list of further ideas, see [TODOs.md](./TODOs.md).
|
2018-01-15 01:25:23 +01:00
|
|
|
|
2017-10-29 23:46:11 +01:00
|
|
|
## Supported datatypes
|
|
|
|
The following table shows the mapping of supported C++ types to supported JSON types:
|
|
|
|
|
2019-12-27 01:40:34 +01:00
|
|
|
| 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 |
|
2020-06-26 22:08:05 +02:00
|
|
|
| `std::string_view` | string/null |
|
|
|
|
| `const char *` | string/null |
|
2019-12-27 01:40:34 +01:00
|
|
|
| iteratable lists (`std::vector`, `std::list`, ...) | array |
|
|
|
|
| sets (`std::set`, `std::unordered_set`, `std::multiset`, ...) | array |
|
2019-12-27 01:43:28 +01:00
|
|
|
| `std::pair`, `std::tuple` | array |
|
2019-12-27 01:40:34 +01:00
|
|
|
| `std::unique_ptr`, `std::shared_ptr` | depends/null |
|
|
|
|
| `std::map`, `std::unordered_map`, `std::multimap`, `std::unordered_multimap` | object |
|
|
|
|
| `std::variant` | object |
|
|
|
|
| `JsonSerializable` | object |
|
2017-10-29 23:46:11 +01:00
|
|
|
|
|
|
|
### Remarks
|
2017-11-12 21:09:45 +01:00
|
|
|
* Raw pointer are not supported. This prevents
|
2018-01-24 19:37:25 +01:00
|
|
|
forgetting to free memory which would have to be allocated when deserializing.
|
2020-06-26 22:08:05 +02:00
|
|
|
* For the same reason `const char *` and `std::string_view` are only supported for serialization.
|
2017-12-22 20:29:38 +01:00
|
|
|
* 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.
|
2017-11-12 21:09:45 +01:00
|
|
|
* The JSON type for smart pointer depends on the type the pointer refers to. It can also be `null`.
|
2019-10-05 01:31:38 +02:00
|
|
|
* If multiple `std::shared_ptr` instances point to the same object this object is serialized multiple times.
|
2018-11-01 05:54:33 +01:00
|
|
|
When deserializing those identical objects, it is currently not possible to share the memory (again). So each
|
2019-10-05 01:31:38 +02:00
|
|
|
`std::shared_ptr` will point to its own copy. Note that this limitation is *not* present when using binary
|
|
|
|
(de)serialization instead of JSON.
|
2017-12-24 01:07:52 +01:00
|
|
|
* 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.
|
2017-12-31 00:48:51 +01:00
|
|
|
* constant member variables are skipped.
|
2018-10-29 23:21:07 +01:00
|
|
|
* It is possible to treat custom types as set/map using the macro `REFLECTIVE_RAPIDJSON_TREAT_AS_MAP_OR_HASH`,
|
2018-10-29 23:23:56 +01:00
|
|
|
`REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_MAP_OR_HASH`, `REFLECTIVE_RAPIDJSON_TREAT_AS_SET` or
|
|
|
|
`REFLECTIVE_RAPIDJSON_TREAT_AS_MULTI_SET`.
|
2019-12-27 01:40:34 +01:00
|
|
|
* 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".
|
2019-11-03 22:02:56 +01:00
|
|
|
* 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.
|
2017-11-13 20:16:43 +01:00
|
|
|
* For custom (de)serialization, see the section below.
|
2018-11-01 05:54:33 +01:00
|
|
|
* 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.
|
2017-10-29 23:46:11 +01:00
|
|
|
|
2020-06-29 20:59:01 +02:00
|
|
|
|
2017-10-24 18:00:51 +02:00
|
|
|
## Usage
|
|
|
|
This example shows how the library can be used to make a `struct` serializable:
|
2018-02-05 21:32:05 +01:00
|
|
|
<pre>
|
2018-07-22 20:51:54 +02:00
|
|
|
#include <reflective_rapidjson/json/serializable.h>
|
2017-10-25 20:03:58 +02:00
|
|
|
|
|
|
|
// define structures, eg.
|
2018-07-22 20:51:54 +02:00
|
|
|
struct TestObject : public ReflectiveRapidJSON::JsonSerializable<TestObject> {
|
2017-10-25 20:03:58 +02:00
|
|
|
int number;
|
|
|
|
double number2;
|
2018-07-22 20:51:54 +02:00
|
|
|
vector<int> numbers;
|
2017-10-25 20:03:58 +02:00
|
|
|
string text;
|
|
|
|
bool boolean;
|
|
|
|
};
|
2018-07-22 20:51:54 +02:00
|
|
|
struct NestingObject : public ReflectiveRapidJSON::JsonSerializable<NestingObject> {
|
2017-10-25 20:03:58 +02:00
|
|
|
string name;
|
|
|
|
TestObject testObj;
|
|
|
|
};
|
2018-07-22 20:51:54 +02:00
|
|
|
struct NestingArray : public ReflectiveRapidJSON::JsonSerializable<NestingArray> {
|
2017-10-25 20:03:58 +02:00
|
|
|
string name;
|
2018-07-22 20:51:54 +02:00
|
|
|
vector<TestObject> testObjects;
|
2017-10-25 20:03:58 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// serialize to JSON
|
|
|
|
NestingArray obj{ ... };
|
2018-07-22 20:51:54 +02:00
|
|
|
cout << "JSON: " << obj.toJson().GetString();
|
2017-10-25 20:03:58 +02:00
|
|
|
|
|
|
|
// deserialize from JSON
|
|
|
|
const auto obj = NestingArray::fromJson(...);
|
|
|
|
|
|
|
|
// in exactly one of the project's translation units
|
|
|
|
#include "reflection/code-defining-structs.h"
|
2018-02-05 21:32:05 +01:00
|
|
|
</pre>
|
2017-10-24 18:00:51 +02:00
|
|
|
|
2017-10-25 20:03:58 +02:00
|
|
|
Note that the header included at the bottom must be generated by invoking the code generator appropriately, eg.:
|
2018-02-05 21:32:05 +01:00
|
|
|
<pre>
|
|
|
|
reflective_rapidjson_generator \
|
|
|
|
--input-file "$srcdir/code-defining-structs.cpp" \
|
|
|
|
--output-file "$builddir/reflection/code-defining-structs.h"
|
|
|
|
</pre>
|
2017-10-24 18:00:51 +02:00
|
|
|
|
2018-11-01 05:54:33 +01:00
|
|
|
There are further arguments available, see:
|
|
|
|
<pre>
|
|
|
|
reflective_rapidjson_generator --help
|
|
|
|
</pre>
|
|
|
|
|
2020-06-29 20:59:01 +02:00
|
|
|
### 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).
|
|
|
|
|
2018-06-23 17:25:30 +02:00
|
|
|
#### Binary (de)serialization
|
|
|
|
It works very similar to the example above. Just use the `BinarySerializable` class instead (or in addition):
|
|
|
|
|
|
|
|
<pre>
|
2018-07-22 20:51:54 +02:00
|
|
|
#include <reflective_rapidjson/binary/serializable.h>
|
|
|
|
struct TestObject : public ReflectiveRapidJSON::BinarySerializable<TestObject>
|
2018-06-23 17:25:30 +02:00
|
|
|
</pre>
|
|
|
|
|
2017-11-17 21:42:49 +01:00
|
|
|
#### Invoking code generator with CMake macro
|
2018-01-24 19:37:25 +01:00
|
|
|
It is possible to use the provided CMake macro to automate the code generator invocation:
|
2018-02-05 21:32:05 +01:00
|
|
|
<pre>
|
2018-01-20 19:47:09 +01:00
|
|
|
# find the package and make macro available
|
2020-01-12 00:33:02 +01:00
|
|
|
find_package(reflective_rapidjson REQUIRED)
|
2017-10-25 20:03:58 +02:00
|
|
|
list(APPEND CMAKE_MODULE_PATH ${REFLECTIVE_RAPIDJSON_MODULE_DIRS})
|
|
|
|
include(ReflectionGenerator)
|
|
|
|
|
2018-02-05 21:32:05 +01:00
|
|
|
# "link" against reflective_rapidjson
|
|
|
|
# it is a header-only lib so this will only add the required include paths
|
|
|
|
# to your target
|
2018-01-20 19:47:09 +01:00
|
|
|
target_link_libraries(mytarget PRIVATE reflective_rapidjson)
|
|
|
|
|
|
|
|
# invoke macro
|
2017-10-25 20:03:58 +02:00
|
|
|
add_reflection_generator_invocation(
|
|
|
|
INPUT_FILES code-defining-structs.cpp
|
2019-10-05 01:31:38 +02:00
|
|
|
GENERATORS json binary
|
2017-10-25 20:03:58 +02:00
|
|
|
OUTPUT_LISTS LIST_OF_GENERATED_HEADERS
|
2018-01-20 19:47:09 +01:00
|
|
|
CLANG_OPTIONS_FROM_TARGETS mytarget
|
2017-10-25 20:03:58 +02:00
|
|
|
)
|
2018-02-05 21:32:05 +01:00
|
|
|
</pre>
|
2017-10-24 18:00:51 +02:00
|
|
|
|
2017-10-25 20:03:58 +02:00
|
|
|
This will produce the file `code-defining-structs.h` in the directory `reflection` in the current build directory. So
|
|
|
|
make sure the current build directory is added to the include directories of your target. The default output directory can
|
|
|
|
also be overridden by passing `OUTPUT_DIRECTORY custom/directory` to the arguments.
|
|
|
|
|
|
|
|
It is possible to specify multiple input files at once. A separate output file is generated for each input. The output files
|
2018-02-05 21:32:05 +01:00
|
|
|
will always have the extension `.h`, independently of the extension of the input file.
|
2017-10-25 20:03:58 +02:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2019-10-05 01:31:38 +02:00
|
|
|
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.
|
|
|
|
|
2018-02-03 16:08:44 +01:00
|
|
|
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.
|
|
|
|
|
2018-01-20 19:47:09 +01:00
|
|
|
For an explanation of the `CLANG_OPTIONS_FROM_TARGETS` argument, read the next section.
|
|
|
|
|
2017-11-17 21:42:49 +01:00
|
|
|
#### Passing Clang options
|
|
|
|
It is possible to pass additional options to the Clang tool invocation used by the code generator.
|
|
|
|
This can be done using the `--clang-opt` argument or the `CLANG_OPTIONS` argument when using the CMake macro.
|
|
|
|
|
2018-02-03 16:26:21 +01:00
|
|
|
For example, additional definitions could be added using `--clang-opt -DSOME_DEFINE -DANOTHER_DEFINE`.
|
|
|
|
But it is actually possible to pass anything from `clang --help`, including the `-X...` options.
|
|
|
|
|
|
|
|
##### Specifying Clang's resource directory
|
|
|
|
In case you get a massive number of errors, ensure Clang's resource directory can be located.
|
|
|
|
[Clang documentation](https://clang.llvm.org/docs/LibTooling.html#libtooling-builtin-includes):
|
|
|
|
|
|
|
|
> The default location to look for builtin headers is in a path `$(dirname /path/to/tool)/../lib/clang/3.3/include` relative to the tool binary.
|
|
|
|
|
|
|
|
To adjust the default location, just add eg. `--clang-opt -resource-dir /usr/lib/clang/5.0.1` to the arguments.
|
|
|
|
|
|
|
|
##### Pass options from regular targets
|
2018-01-20 19:47:09 +01:00
|
|
|
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
|
2020-01-12 00:33:02 +01:00
|
|
|
from certain targets to the code generator. Those targets can be specified using the macro's `CLANG_OPTIONS_FROM_TARGETS` argument.
|
2018-01-20 19:47:09 +01:00
|
|
|
|
|
|
|
#### Notes regarding cross-compilation
|
|
|
|
* For cross compilation, it is required to build the code generator for the platform you're building on.
|
|
|
|
* Since the code generator is likely not required under the target platform, you should add `-DNO_GENERATOR:BOOL=ON` to the CMake
|
|
|
|
arguments when building Reflective RapidJSON for the target platform.
|
2018-01-24 19:37:25 +01:00
|
|
|
* When using the `add_reflection_generator_invocation` macro, you need to set the following CMake cache variables:
|
2018-02-05 21:32:05 +01:00
|
|
|
* `REFLECTION_GENERATOR_EXECUTABLE:FILEPATH=/path/to/reflective_rapidjson_generator`
|
2018-02-03 15:44:10 +01:00
|
|
|
* specifies the path of the code generator executable built for the platform you're building on
|
2018-02-05 21:32:05 +01:00
|
|
|
* only required if the executable is not in the path anyways
|
2018-02-03 15:44:10 +01:00
|
|
|
* `REFLECTION_GENERATOR_TRIPLE:STRING=machine-vendor-operatingsystem`
|
|
|
|
* specifies the GNU platform triple for the target platform
|
2018-02-05 21:32:05 +01:00
|
|
|
* examples for cross compiling with mingw-w64 under GNU/Linux:
|
2018-02-03 15:44:10 +01:00
|
|
|
`x86_64-w64-mingw32`, `i686-w64-mingw32`
|
|
|
|
* `REFLECTION_GENERATOR_INCLUDE_DIRECTORIES:STRING=/custom/prefix/include`
|
2018-02-05 21:32:05 +01:00
|
|
|
* implicit include directories for target platform
|
|
|
|
* example for cross compiling with mingw-w64 under GNU/Linux:
|
2018-02-03 15:44:10 +01:00
|
|
|
`/usr/lib/gcc/x86_64-w64-mingw32/7.2.1/include;/usr/x86_64-w64-mingw32/include/c++/7.2.1/x86_64-w64-mingw32;/usr/x86_64-w64-mingw32/include`
|
2018-01-20 19:47:09 +01:00
|
|
|
* The Arch Linux packages mentioned at the end of the README file also include `mingw-w64` variants which give a concrete example how
|
|
|
|
cross-compilation can be done.
|
2017-11-17 21:42:49 +01:00
|
|
|
|
2017-10-25 20:03:58 +02:00
|
|
|
### Using Boost.Hana instead of the code generator
|
|
|
|
The same example as above. However, this time Boost.Hana is used - so it doesn't require invoking the generator.
|
2017-10-24 18:00:51 +02:00
|
|
|
|
2018-02-05 21:32:05 +01:00
|
|
|
<pre>
|
2019-12-06 16:52:10 +01:00
|
|
|
#include <reflective_rapidjson/json/serializable-boosthana.h>
|
2017-10-25 20:03:58 +02:00
|
|
|
|
|
|
|
// define structures using BOOST_HANA_DEFINE_STRUCT, eg.
|
2018-07-22 20:51:54 +02:00
|
|
|
struct TestObject : public JsonSerializable<TestObject> {
|
2017-10-25 20:03:58 +02:00
|
|
|
BOOST_HANA_DEFINE_STRUCT(TestObject,
|
|
|
|
(int, number),
|
|
|
|
(double, number2),
|
2018-07-22 20:51:54 +02:00
|
|
|
(vector<int>, numbers),
|
2017-10-25 20:03:58 +02:00
|
|
|
(string, text),
|
|
|
|
(bool, boolean)
|
|
|
|
);
|
|
|
|
};
|
2018-07-22 20:51:54 +02:00
|
|
|
struct NestingObject : public JsonSerializable<NestingObject> {
|
2017-10-25 20:03:58 +02:00
|
|
|
BOOST_HANA_DEFINE_STRUCT(NestingObject,
|
|
|
|
(string, name),
|
|
|
|
(TestObject, testObj)
|
|
|
|
);
|
|
|
|
};
|
2018-07-22 20:51:54 +02:00
|
|
|
struct NestingArray : public JsonSerializable<NestingArray> {
|
2017-10-25 20:03:58 +02:00
|
|
|
BOOST_HANA_DEFINE_STRUCT(NestingArray,
|
|
|
|
(string, name),
|
2018-07-22 20:51:54 +02:00
|
|
|
(vector<TestObject>, testObjects)
|
2017-10-25 20:03:58 +02:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
// serialize to JSON
|
|
|
|
NestingArray obj{ ... };
|
2018-07-22 20:51:54 +02:00
|
|
|
cout << "JSON: " << obj.toJson().GetString();
|
2017-10-25 20:03:58 +02:00
|
|
|
|
|
|
|
// deserialize from JSON
|
|
|
|
const auto obj = NestingArray::fromJson(...);
|
2018-02-05 21:32:05 +01:00
|
|
|
</pre>
|
2017-10-24 18:00:51 +02:00
|
|
|
|
2017-10-28 16:23:39 +02:00
|
|
|
So beside the `BOOST_HANA_DEFINE_STRUCT` macro, the usage remains the same.
|
2017-10-25 20:03:58 +02:00
|
|
|
|
2017-11-02 15:15:29 +01:00
|
|
|
#### Disadvantages
|
|
|
|
* Use of ugly macro required
|
|
|
|
* No context information for errors like type-mismatch available
|
|
|
|
* Inherited members not considered
|
2018-01-24 19:37:25 +01:00
|
|
|
* Proper support for enums is unlikely
|
2017-11-02 15:15:29 +01:00
|
|
|
|
2017-11-06 21:25:45 +01:00
|
|
|
### Enable reflection for 3rd party classes/structs
|
|
|
|
It is obvious that the previously shown examples do not work for classes
|
|
|
|
defined in 3rd party header files as it requires adding an additional
|
|
|
|
base class.
|
|
|
|
|
|
|
|
To work around this issue, one can use the `REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE`
|
|
|
|
macro. It will enable the `toJson` and `fromJson` methods for the specified class
|
|
|
|
in the `ReflectiveRapidJSON::JsonReflector` namespace:
|
|
|
|
|
2018-02-05 21:32:05 +01:00
|
|
|
<pre>
|
2017-11-06 21:25:45 +01:00
|
|
|
// somewhere in included header
|
|
|
|
struct ThridPartyStruct
|
|
|
|
{ ... };
|
|
|
|
|
|
|
|
// somewhere in own header or source file
|
|
|
|
REFLECTIVE_RAPIDJSON_MAKE_JSON_SERIALIZABLE(ThridPartyStruct)
|
|
|
|
|
|
|
|
// (de)serialization
|
|
|
|
ReflectiveRapidJSON::JsonReflector::toJson(...).GetString();
|
2018-07-22 20:51:54 +02:00
|
|
|
ReflectiveRapidJSON::JsonReflector::fromJson<ThridPartyStruct>("...");
|
2018-02-05 21:32:05 +01:00
|
|
|
</pre>
|
2017-11-06 21:25:45 +01:00
|
|
|
|
2017-11-09 01:03:34 +01:00
|
|
|
The code generator will emit the code in the same way as if `JsonSerializable` was
|
2017-11-06 21:25:45 +01:00
|
|
|
used.
|
|
|
|
|
2017-11-09 01:03:34 +01:00
|
|
|
By the way, the functions in the `ReflectiveRapidJSON::JsonReflector` namespace can also
|
|
|
|
be used when inheriting from `JsonSerializable` (instead of the member functions).
|
|
|
|
|
|
|
|
### (De)serializing private members
|
|
|
|
By default, private members are not considered for (de)serialization. However, it is possible
|
|
|
|
to enable this by adding `friend` methods for the helper functions of Reflective RapidJSON.
|
|
|
|
|
|
|
|
To make things easier, there's a macro provided:
|
2018-02-05 21:32:05 +01:00
|
|
|
<pre>
|
2018-07-22 20:51:54 +02:00
|
|
|
struct SomeStruct : public JsonSerializable<SomeStruct> {
|
2017-11-09 01:03:34 +01:00
|
|
|
REFLECTIVE_RAPIDJSON_ENABLE_PRIVATE_MEMBERS(SomeStruct);
|
|
|
|
|
|
|
|
public:
|
|
|
|
std::string publicMember = "will be (de)serialized anyways";
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::string privateMember = "will be (de)serialized with the help of REFLECTIVE_RAPIDJSON_ENABLE_PRIVATE_MEMBERS macro";
|
|
|
|
};
|
2018-02-05 21:32:05 +01:00
|
|
|
</pre>
|
2017-11-09 01:03:34 +01:00
|
|
|
|
|
|
|
#### Caveats
|
|
|
|
* It will obviously not work for 3rd party structs.
|
|
|
|
* This way to allow (de)serialization of private members must be applied when using Boost.Hana
|
|
|
|
and there are any private members present. The reason is that accessing the private members can
|
|
|
|
currently not prevented when using Boost.Hana.
|
2017-11-02 15:15:29 +01:00
|
|
|
|
2017-11-03 17:45:16 +01:00
|
|
|
### Custom (de)serialization
|
|
|
|
Sometimes it is appropriate to implement custom (de)serialization. For instance, a
|
|
|
|
custom object representing a time value should likey be serialized as a string rather
|
2018-02-05 21:32:05 +01:00
|
|
|
than an object containing the internal structure.
|
2017-11-03 17:45:16 +01:00
|
|
|
|
|
|
|
An example for such custom (de)serialization can be found in the file
|
|
|
|
`json/reflector-chronoutilities.h`. It provides (de)serialization of `DateTime` and
|
2018-02-05 21:32:05 +01:00
|
|
|
`TimeSpan` objects from the C++ utilities library mentioned under dependencies.
|
2017-11-03 17:45:16 +01:00
|
|
|
|
2017-11-13 16:29:23 +01:00
|
|
|
### Remarks
|
2018-02-05 21:32:05 +01:00
|
|
|
* Static member variables and member functions are currently ignored by the generator.
|
|
|
|
* It is currently not possible to ignore a specific member variable.
|
2017-11-13 16:29:23 +01:00
|
|
|
|
2017-11-09 01:03:34 +01:00
|
|
|
### Further examples
|
2018-01-15 01:25:23 +01:00
|
|
|
* Checkout the test cases for further examples. Relevant files are in
|
|
|
|
the directories `lib/tests` and `generator/tests`.
|
|
|
|
* There's also my
|
|
|
|
[tag editor](https://github.com/Martchus/tageditor), which uses Reflective RapidJSON to provide
|
|
|
|
a JSON export.
|
|
|
|
See [json.h](https://github.com/Martchus/tageditor/blob/master/cli/json.h) and
|
|
|
|
[mainfeatures.cpp#exportToJson](https://github.com/Martchus/tageditor/blob/master/cli/mainfeatures.cpp#L856).
|
2017-11-09 01:03:34 +01:00
|
|
|
|
2018-01-12 15:28:37 +01:00
|
|
|
## Architecture
|
|
|
|
The following diagram gives an overview about the architecture of the code generator and wrapper library
|
|
|
|
around RapidJSON:
|
|
|
|
|
2018-02-26 22:37:43 +01:00
|
|
|
![Architectue overview](./doc/arch.svg)
|
2018-01-12 15:28:37 +01:00
|
|
|
|
|
|
|
* blue: classes from LibTooling/Clang
|
|
|
|
* grey: conceivable extension or use
|
|
|
|
|
2017-10-24 18:00:51 +02:00
|
|
|
## Install instructions
|
|
|
|
|
|
|
|
### Dependencies
|
2017-11-02 15:15:29 +01:00
|
|
|
The following dependencies are required at build time. Note that Reflective RapidJSON itself
|
|
|
|
and *none* of these dependencies are required at runtime by an application which makes use of
|
|
|
|
Reflective RapidJSON.
|
|
|
|
|
2021-01-15 15:27:28 +01:00
|
|
|
* C++ compiler and C++ standard library supporting at least C++17
|
2017-11-02 15:15:29 +01:00
|
|
|
* the [CMake](https://cmake.org) build system
|
2018-01-07 00:56:12 +01:00
|
|
|
* LibTooling from [Clang](https://clang.llvm.org) for the code generator (optional when using
|
|
|
|
Boost.Hana)
|
2017-11-02 15:15:29 +01:00
|
|
|
* [RapidJSON](https://github.com/Tencent/rapidjson) for JSON (de)serialization
|
|
|
|
* [C++ utilities](https://github.com/Martchus/cpp-utilities) for various helper functions
|
|
|
|
|
|
|
|
#### Optional
|
2018-01-07 00:56:12 +01:00
|
|
|
* [Boost.Hana](http://www.boost.org/doc/libs/1_65_1/libs/hana/doc/html/index.html) for using
|
|
|
|
`BOOST_HANA_DEFINE_STRUCT` instead of code generator
|
2017-11-02 15:15:29 +01:00
|
|
|
* [CppUnit](https://www.freedesktop.org/wiki/Software/cppunit) for building and running the tests
|
|
|
|
* [Doxygen](http://www.doxygen.org) for generating API documentation
|
|
|
|
* [Graphviz](http://www.graphviz.org) for diagrams in the API documentation
|
|
|
|
|
|
|
|
#### Remarks
|
|
|
|
* It is not required to use CMake as build system for your own project. However, when using a
|
|
|
|
different build system, there is no helper for adding the code generator to the build process
|
|
|
|
provided (so far).
|
2018-01-07 00:56:12 +01:00
|
|
|
* I usually develop using the latest version of those dependencies. So it is recommend to get the
|
2021-01-15 15:24:50 +01:00
|
|
|
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.
|
2018-11-01 05:54:33 +01:00
|
|
|
* The binary (de)serializer requires C++ utilities at runtime. So when using it, it is required to
|
|
|
|
link against C++ utilities.
|
2017-10-24 18:00:51 +02:00
|
|
|
|
2017-11-02 15:15:29 +01:00
|
|
|
### How to build
|
|
|
|
#### 1. Install dependencies
|
|
|
|
Install all required dependencies. Under a typical GNU/Linux system most of these dependencies
|
|
|
|
can be installed via the package manager. Otherwise follow the links in the "Dependencies" section
|
|
|
|
above.
|
2017-10-24 18:00:51 +02:00
|
|
|
|
2017-11-02 15:15:29 +01:00
|
|
|
C++ utilities is likely not available as package. However, it is possible to build C++ utilities
|
|
|
|
together with `reflective-rapidjson` to simplify the build process. The following build script makes
|
|
|
|
use of this. (To use system C++ utilities, just skip any lines with "`c++utilities`" in the following
|
|
|
|
examples.)
|
2017-10-24 18:00:51 +02:00
|
|
|
|
2017-11-02 15:15:29 +01:00
|
|
|
#### 2. Make dependencies available
|
|
|
|
|
|
|
|
When installing (some) of the dependencies at custom locations, it is likely neccassary to tell
|
|
|
|
CMake where to find them. If you installed everything using packages provided by the system,
|
|
|
|
you can skip this step of course.
|
|
|
|
|
|
|
|
To specify custom locations, just set some environment variables before invoking CMake. This
|
|
|
|
can likely be done in your IDE settings and of course at command line. Here is a Bash example:
|
2018-02-05 21:32:05 +01:00
|
|
|
<pre>
|
2017-11-02 15:15:29 +01:00
|
|
|
export PATH=$CUSTOM_INSTALL_PREFIX/bin:$PATH
|
|
|
|
export CMAKE_PREFIX_PATH=$CUSTOM_INSTALL_PREFIX:$CMAKE_PREFIX_PATH
|
|
|
|
export CMAKE_LIBRARY_PATH=$CUSTOM_INSTALL_PREFIX/lib:$CMAKE_LIBRARY_PATH
|
|
|
|
export CMAKE_INCLUDE_PATH=$CUSTOM_INSTALL_PREFIX/include:$CMAKE_INCLUDE_PATH
|
2018-02-05 21:32:05 +01:00
|
|
|
</pre>
|
2017-11-02 15:15:29 +01:00
|
|
|
|
|
|
|
There are also a lot of [useful variables](https://cmake.org/Wiki/CMake_Useful_Variables)
|
|
|
|
that can be specified as CMake arguments. It is also possible to create a
|
|
|
|
[toolchain file](https://cmake.org/cmake/help/v3.10/manual/cmake-toolchains.7.html).
|
2017-10-24 18:00:51 +02:00
|
|
|
|
|
|
|
|
2017-11-02 15:15:29 +01:00
|
|
|
#### 3. Get sources, eg. using Git:
|
2018-02-05 21:32:05 +01:00
|
|
|
<pre>
|
2017-10-28 18:24:12 +02:00
|
|
|
cd $SOURCES
|
|
|
|
git clone https://github.com/Martchus/cpp-utilities.git c++utilities
|
|
|
|
git clone https://github.com/Martchus/reflective-rapidjson.git
|
2018-02-05 21:32:05 +01:00
|
|
|
</pre>
|
2017-10-28 18:24:12 +02:00
|
|
|
|
|
|
|
If you don't want to build the development version, just checkout the desired version tag.
|
|
|
|
|
2017-11-02 15:15:29 +01:00
|
|
|
#### 4. Run the build script
|
|
|
|
Here is an example for building with GNU Make:
|
2018-02-05 21:32:05 +01:00
|
|
|
<pre>
|
2017-10-28 18:24:12 +02:00
|
|
|
cd $BUILD_DIR
|
|
|
|
# generate Makefile
|
|
|
|
cmake \
|
|
|
|
-DCMAKE_BUILD_TYPE:STRING=Release \
|
|
|
|
-DCMAKE_INSTALL_PREFIX:PATH="/final/install/prefix" \
|
|
|
|
-DBUNDLED_CPP_UTILITIES_PATH:PATH="$SOURCES/c++utilities" \
|
|
|
|
"$SOURCES/reflective-rapidjson"
|
|
|
|
# build library and generators
|
|
|
|
make
|
2017-10-28 22:52:31 +02:00
|
|
|
# build and run tests (optional, requires CppUnit)
|
2017-10-28 18:24:12 +02:00
|
|
|
make check
|
2017-10-28 22:52:31 +02:00
|
|
|
# build tests but do not run them (optional, requires CppUnit)
|
|
|
|
make tests
|
|
|
|
# generate API documentation (optional, reqquires Doxygen)
|
2017-10-28 18:24:12 +02:00
|
|
|
make apidoc
|
|
|
|
# install header files, libraries and generator
|
|
|
|
make install DESTDIR="/temporary/install/location"
|
2018-02-05 21:32:05 +01:00
|
|
|
</pre>
|
2017-10-28 18:24:12 +02:00
|
|
|
Add eg. `-j$(nproc)` to `make` arguments for using all cores.
|
2018-01-12 17:01:55 +01:00
|
|
|
|
|
|
|
### Packages
|
2018-04-04 20:30:40 +02:00
|
|
|
* 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)
|
2020-12-05 21:28:22 +01:00
|
|
|
* there is also a [binary repository](https://martchus.no-ip.biz/repo/arch/ownstuff)
|
2018-04-04 20:30:40 +02:00
|
|
|
* Tumbleweed
|
2020-12-05 21:28:22 +01:00
|
|
|
* 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)
|
2018-04-04 20:30:40 +02:00
|
|
|
* 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.
|