Skip to content

Commit 5c81e40

Browse files
Redesigned the writer (#36) and added YAML support based on the new writer (#23)
1 parent 0a524a9 commit 5c81e40

File tree

133 files changed

+3086
-842
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

133 files changed

+3086
-842
lines changed

.github/workflows/test.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ jobs:
1818
- name: Run test
1919
run: |
2020
g++ --version
21-
cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_XML=ON -DCMAKE_BUILD_TYPE=Release
21+
cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release
2222
cmake --build build -j 4
2323
./build/tests/flexbuffers/reflect-cpp-flexbuffers-tests
2424
./build/tests/json/reflect-cpp-json-tests
2525
./build/tests/xml/reflect-cpp-xml-tests
26+
./build/tests/yaml/reflect-cpp-yaml-tests
2627
2728
linux-clang:
2829
runs-on: ubuntu-latest
@@ -46,11 +47,12 @@ jobs:
4647
CXX: clang++
4748
run: |
4849
clang --version
49-
cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_XML=ON -DCMAKE_BUILD_TYPE=Release
50+
cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release
5051
cmake --build build -j 4
5152
./build/tests/flexbuffers/reflect-cpp-flexbuffers-tests
5253
./build/tests/json/reflect-cpp-json-tests
5354
./build/tests/xml/reflect-cpp-xml-tests
55+
./build/tests/yaml/reflect-cpp-yaml-tests
5456
5557
5658
# The latest MSVC version on GitHub Actions has a bug, and it's difficult to switch to another version.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,7 @@
4444
*.json
4545
*.fb
4646
*.xml
47+
*.yml
48+
*.yaml
4749

4850
!vcpkg.json

CMakeLists.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ cmake_minimum_required(VERSION 3.15)
33
option(REFLECTCPP_BUILD_SHARED "Build shared library" OFF)
44
option(REFLECTCPP_FLEXBUFFERS "Enable flexbuffers support" OFF)
55
option(REFLECTCPP_XML "Enable XML support" OFF)
6+
option(REFLECTCPP_YAML "Enable YAML support" OFF)
67

78
option(REFLECTCPP_BUILD_TESTS "Build tests" OFF)
89

910
# enable vcpkg if require features other than JSON
10-
if (REFLECTCPP_FLEXBUFFERS OR REFLECTCPP_XML)
11+
if (REFLECTCPP_FLEXBUFFERS OR REFLECTCPP_XML OR REFLECTCPP_YAML)
1112
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file")
1213
endif ()
1314

@@ -33,6 +34,11 @@ if (REFLECTCPP_XML)
3334
target_link_libraries(reflectcpp INTERFACE pugixml::pugixml)
3435
endif ()
3536

37+
if (REFLECTCPP_YAML)
38+
find_package(yaml-cpp CONFIG REQUIRED)
39+
target_link_libraries(reflectcpp INTERFACE yaml-cpp::yaml-cpp)
40+
endif ()
41+
3642
target_compile_options(reflectcpp PRIVATE -Wall)
3743

3844
if (REFLECTCPP_BUILD_TESTS)

README.md

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,22 @@ Design principles for reflect-cpp include:
2323
- Simple extendability to custom classes
2424
- Standard C++ only, no compiler-specific macros
2525

26-
## Why do we need this?
27-
28-
Suppose your C++ program has complex data structures it needs to save and load. Or maybe it needs to interact with some kind of external API. If you do this the traditional way, you will have a lot of boilerplate code. This is annoying and error-prone.
26+
## Serialization formats
2927

30-
reflect-cpp is not just a reflection library, it is for **serialization, deserialization and validation** through reflection.
28+
reflect-cpp provides a unified reflection-based interface across different serialization formats. It is deliberately designed in a very modular way, using [concepts](https://en.cppreference.com/w/cpp/language/constraints), to make it as easy as possible to interface various C or C++ libraries related to serialization. Refer to the [documentation](https://github.com/getml/reflect-cpp/tree/main/docs) for details.
3129

32-
That means that you can encode your requirements about the input data in the type system and have them validated upfront. This is why the library also includes algebraic data types like tagged unions and numerous validation routines. Having your requirements encoded in the type system is the most reliable way of ensuring they are met. If your requirements are not met, the user of your software gets a very clear error message. Encoding your requirements in the type system also makes it a lot easier for anyone reading your code.
30+
The following table lists the serialization formats currently supported by reflect-cpp and the underlying libraries used:
3331

34-
This increases user experience and developer experience, it makes your code safer (fewer bugs) and more secure (less prone to malicious attacks).
32+
| Format | Library | Version | License | Remarks |
33+
|--------------|------------------------------------------------------|--------------|------------| -----------------------------------------------------|
34+
| JSON | [YYJSON](https://github.com/ibireme/yyjson) | >= 0.8.0 | MIT | out-of-the-box support, included in this repository |
35+
| flexbuffers | [flatbuffers](https://github.com/google/flatbuffers) | >= 23.5.26 | Apache 2.0 | |
36+
| XML | [pugixml](https://github.com/zeux/pugixml) | >= 1.14 | MIT | |
37+
| YAML | [yaml-cpp](https://github.com/jbeder/yaml-cpp) | >= 0.8.0 | MIT | |
3538

36-
For a more in-depth theoretical discussions of these topics, the following books are warmly recommended:
39+
Support for more serialization formats is in development. Refer to the [issues](https://github.com/getml/reflect-cpp/issues) for details.
3740

38-
- *Category Theory for Programmers* by Bartosz Milewski (https://github.com/hmemcpy/milewski-ctfp-pdf/releases)
39-
- *Domain Modeling Made Functional* by Scott Wlaschin
41+
Please also refer to the *vcpkg.json* in this repository.
4042

4143
## Simple Example
4244

@@ -66,6 +68,26 @@ The resulting JSON string looks like this:
6668
{"first_name":"Homer","last_name":"Simpson","age":45}
6769
```
6870

71+
Or you can use another format, such as YAML. This will work for just about
72+
any example in the entire documentation or any supported format.
73+
74+
```cpp
75+
#include <rfl/yaml.hpp>
76+
77+
// ... (same as above)
78+
79+
const std::string yaml_string = rfl::yaml::write(homer);
80+
auto homer2 = rfl::yaml::read<Person>(yaml_string).value();
81+
```
82+
83+
The resulting YAML string looks like this:
84+
85+
```yaml
86+
first_name: Homer
87+
last_name: Simpson
88+
age: 45
89+
```
90+
6991
## More Comprehensive Example
7092
7193
```cpp
@@ -361,19 +383,20 @@ In addition, it supports the following custom containers:
361383
362384
Finally, it is very easy to extend full support to your own classes, refer to the [documentation](https://github.com/getml/reflect-cpp/tree/main/docs) for details.
363385
364-
## Serialization formats
386+
## Why do we need this?
365387
366-
reflect-cpp is deliberately designed in a very modular way, using [concepts](https://en.cppreference.com/w/cpp/language/constraints), to make it as easy as possible to interface C or C++ libraries for various serialization formats. Refer to the [documentation](https://github.com/getml/reflect-cpp/tree/main/docs) for details. PRs related to serialization formats are welcome.
388+
Suppose your C++ program has complex data structures it needs to save and load. Or maybe it needs to interact with some kind of external API. If you do this the traditional way, you will have a lot of boilerplate code. This is annoying and error-prone.
367389
368-
The following table lists the serialization formats currently supported by reflect-cpp and the underlying libraries used:
390+
reflect-cpp is not just a reflection library, it is for **serialization, deserialization and validation** through reflection.
369391
370-
| Format | Library | Version | License | Remarks |
371-
|--------------|------------------------------------------------------|--------------|------------| -----------------------------------------------------|
372-
| JSON | [YYJSON](https://github.com/ibireme/yyjson) | >= 0.8.0 | MIT | out-of-the-box support, included in this repository |
373-
| flexbuffers | [flatbuffers](https://github.com/google/flatbuffers) | >= 23.5.26#1 | Apache 2.0 | |
374-
| XML | [pugixml](https://github.com/zeux/pugixml) | >= 1.14 | MIT | |
392+
That means that you can encode your requirements about the input data in the type system and have them validated upfront. This is why the library also includes algebraic data types like tagged unions and numerous validation routines. Having your requirements encoded in the type system is the most reliable way of ensuring they are met. If your requirements are not met, the user of your software gets a very clear error message. Encoding your requirements in the type system also makes it a lot easier for anyone reading your code.
375393
376-
Please also refer to the *vcpkg.json* in this repository.
394+
This increases user experience and developer experience, it makes your code safer (fewer bugs) and more secure (less prone to malicious attacks).
395+
396+
For a more in-depth theoretical discussions of these topics, the following books are warmly recommended:
397+
398+
- *Category Theory for Programmers* by Bartosz Milewski (https://github.com/hmemcpy/milewski-ctfp-pdf/releases)
399+
- *Domain Modeling Made Functional* by Scott Wlaschin
377400
378401
## Documentation
379402
@@ -427,6 +450,7 @@ add_subdirectory(reflect-cpp) # Add this project as a subdirectory
427450
428451
set(REFLECTCPP_FLEXBUFFERS ON) # Optional
429452
set(REFLECTCPP_XML ON) # Optional
453+
set(REFLECTCPP_YAML ON) # Optional
430454
431455
target_link_libraries(your_project PRIVATE reflectcpp) # Link against the library
432456
```
@@ -470,7 +494,7 @@ git submodule update --init
470494
./vcpkg/bootstrap-vcpkg.bat # Windows
471495
# You may be prompted to install additional dependencies.
472496

473-
cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_XML=ON -DCMAKE_BUILD_TYPE=Release
497+
cmake -S . -B build -DREFLECTCPP_BUILD_TESTS=ON -DREFLECTCPP_FLEXBUFFERS=ON -DREFLECTCPP_XML=ON -DREFLECTCPP_YAML=ON -DCMAKE_BUILD_TYPE=Release
474498
cmake --build build -j 4 # gcc, clang
475499
cmake --build build --config Release -j 4 # MSVC
476500
```
@@ -481,6 +505,7 @@ To run the tests, do the following:
481505
./build/tests/flexbuffers/reflect-cpp-flexbuffers-tests
482506
./build/tests/json/reflect-cpp-json-tests
483507
./build/tests/xml/reflect-cpp-xml-tests
508+
./build/tests/xml/reflect-cpp-yaml-tests
484509
```
485510

486511
## How to contribute

docs/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060

6161
5.3) [XML](https://github.com/getml/reflect-cpp/blob/main/docs/xml.md)
6262

63+
5.4) [YAML](https://github.com/getml/reflect-cpp/blob/main/docs/yaml.md)
64+
6365
## 6) Advanced topics
6466

6567
6.1) [Supporting your own format](https://github.com/getml/reflect-cpp/blob/main/docs/supporting_your_own_format.md) - For supporting your own serialization and deserialization formats.

docs/supporting_your_own_format.md

Lines changed: 84 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -73,29 +73,82 @@ struct Writer {
7373
using OutputObjectType = ...;
7474
using OutputVarType = ...;
7575
76-
/// Appends `_var` to the end of `_arr`, thus mutating it.
77-
void add(const OutputVarType _var, OutputArrayType* _arr) const noexcept {...}
78-
79-
/// Returns an empty OutputVarType, the NULL type of the format.
80-
OutputVarType empty_var() const noexcept {...}
81-
82-
/// Generates an OutputVarType from a basic type
83-
/// (std::string, bool, floating point or integral).
84-
template <class T>
85-
OutputVarType from_basic_type(const T& _var) const noexcept {...}
86-
87-
/// Generates a new, empty array.
88-
OutputArrayType new_array() const noexcept {...}
89-
90-
/// Generates a new, empty object.
91-
OutputObjectType new_object() const noexcept {...}
92-
93-
/// Determines whether the var is empty (whether it is the NULL type).
94-
bool is_empty(const OutputVarType& _var) const noexcept {...}
95-
96-
/// Adds a new field to obj, thus mutating it.
97-
void set_field(const std::string& _name, const OutputVarType& _var,
98-
OutputObjectType* _obj) const noexcept {...}
76+
/// Sets an empty array as the root element of the document.
77+
/// Some serialization formats require you to pass the expected size in
78+
/// advance. If you are not working with such a format, you can ignore the
79+
/// parameter `_size`. Returns the new array for further modification.
80+
OutputArrayType array_as_root(const size_t _size) const noexcept;
81+
82+
/// Sets an empty object as the root element of the document.
83+
/// Some serialization formats require you to pass the expected size in
84+
/// advance. If you are not working with such a format, you can ignore the
85+
/// parameter `_size`.
86+
/// Returns the new object for further modification.
87+
OutputObjectType object_as_root(const size_t _size) const noexcept;
88+
89+
/// Sets a null as the root element of the document. Returns OutputVarType
90+
/// containing the null value.
91+
OutputVarType null_as_root() const noexcept;
92+
93+
/// Sets a basic value (bool, numeric, string) as the root element of the
94+
/// document. Returns an OutputVarType containing the new value.
95+
template <class T>
96+
OutputVarType value_as_root(const T& _var) const noexcept;
97+
98+
/// Adds an empty array to an existing array. Returns the new
99+
/// array for further modification.
100+
OutputArrayType add_array_to_array(const size_t _size,
101+
OutputArrayType* _parent) const noexcept;
102+
103+
/// Adds an empty array to an existing object. The key or name of the field is
104+
/// signified by `_name`. Returns the new array for further modification.
105+
OutputArrayType add_array_to_object(
106+
const std::string& _name, const size_t _size,
107+
OutputObjectType* _parent) const noexcept;
108+
109+
/// Adds an empty object to an existing array. Returns the new
110+
/// object for further modification.
111+
OutputObjectType add_object_to_array(
112+
const size_t _size, OutputArrayType* _parent) const noexcept;
113+
114+
/// Adds an empty object to an existing object. The key or name of the field
115+
/// is signified by `_name`. Returns the new object for further modification.
116+
OutputObjectType add_object_to_object(
117+
const std::string& _name, const size_t _size,
118+
OutputObjectType* _parent) const noexcept;
119+
120+
/// Adds a basic value (bool, numeric, string) to an array. Returns an
121+
/// OutputVarType containing the new value.
122+
template <class T>
123+
OutputVarType add_value_to_array(const T& _var,
124+
OutputArrayType* _parent) const noexcept;
125+
126+
/// Adds a basic value (bool, numeric, string) to an existing object. The key
127+
/// or name of the field is signified by `name`. Returns an
128+
/// OutputVarType containing the new value.
129+
template <class T>
130+
OutputVarType add_value_to_object(const std::string& _name, const T& _var,
131+
OutputObjectType* _parent) const noexcept;
132+
133+
/// Adds a null value to an array. Returns an
134+
/// OutputVarType containing the null value.
135+
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept;
136+
137+
/// Adds a null value to an existing object. The key
138+
/// or name of the field is signified by `name`. Returns an
139+
/// OutputVarType containing the null value.
140+
OutputVarType add_null_to_object(const std::string& _name,
141+
OutputObjectType* _parent) const noexcept;
142+
143+
/// Signifies to the writer that we do not want to add any further elements to
144+
/// this array. Some serialization formats require this. If you are working
145+
/// with a serialization format that doesn't, just leave the function empty.
146+
void end_array(OutputArrayType* _arr) const noexcept;
147+
148+
/// Signifies to the writer that we do not want to add any further elements to
149+
/// this object. Some serialization formats require this. If you are working
150+
/// with a serialization format that doesn't, just leave the function empty.
151+
void end_object(OutputObjectType* _obj) const noexcept;
99152
};
100153
```
101154

@@ -145,12 +198,12 @@ struct Reader {
145198
/// Returns an rfl::Error if `_var` cannot be cast as an array.
146199
rfl::Result<InputArrayType> to_array(const InputVarType& _var) const noexcept {...}
147200

148-
/// fct is a function that turns the field name into the field index of the
201+
/// _fct is a function that turns the field name into the field index of the
149202
/// struct. It returns -1, if the fields does not exist on the struct. This
150203
/// returns an std::array that can be used to build up the struct.
151204
/// See below for a more comprehensive explanation.
152205
template <size_t size, class FunctionType>
153-
std::array<InputVarType, size> to_fields_array(
206+
std::array<std::optional<InputVarType>, size> to_fields_array(
154207
const FunctionType _fct, const InputObjectType& _obj) const noexcept {...}
155208

156209
/// Iterates through an object and writes the contained key-value pairs into
@@ -187,15 +240,15 @@ Consider the following struct:
187240
188241
```cpp
189242
struct Person {
190-
rfl::Field<"firstName", std::string> first_name;
191-
rfl::Field<"lastName", std::string> last_name;
192-
rfl::Field<"birthday", rfl::Timestamp<"%Y-%m-%d">> birthday;
193-
rfl::Field<"children", std::vector<Person>> children;
243+
rfl::Rename<"firstName", std::string> first_name;
244+
rfl::Rename<"lastName", std::string> last_name;
245+
rfl::Timestamp<"%Y-%m-%d"> birthday;
246+
std::vector<Person> children;
194247
};
195248
```
196249

197250
This struct contains four fields. `rfl::parsing::Parser` expects `to_fields_array`
198-
to return an `std::array<InputVarType, 4>` containing the field "firstName" in the
251+
to return an `std::array<std::optional<InputVarType>, 4>` containing the field "firstName" in the
199252
first position, "lastName" in the second position, "birthday" in the third position
200253
and "children" in the fourth position.
201254

@@ -214,6 +267,4 @@ Your job is to implement the following:
214267
1. Iterate through `_obj`.
215268
2. Identify the required index of the field name using `_fct`.
216269
3. Set the corresponding field in `std::array` to the field value associated with the field name.
217-
4. Any field that could not be set in steps 1-3 must be set to the NULL value,
218-
such that `Reader.is_empty(...)` would return `true`.
219-
270+
4. Any field that could not be set in steps 1-3 must be set to `std::nullopt`.

0 commit comments

Comments
 (0)