Skip to content

Commit 813ffbd

Browse files
Merge branch 'main' into f/toml
2 parents 2a3dae8 + 1f2e216 commit 813ffbd

File tree

5 files changed

+229
-5
lines changed

5 files changed

+229
-5
lines changed

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ age: 45
9090
```
9191
9292
This will work for just about any example in the entire documentation
93-
and any supported format:
93+
and any supported format, except where explicitly noted otherwise:
9494
9595
```cpp
9696
rfl::bson::write(homer);
@@ -200,6 +200,35 @@ Found 5 errors:
200200
5) Field named 'children' not found.
201201
```
202202

203+
## JSON schema
204+
205+
reflect-cpp also supports generating JSON schemata:
206+
207+
```cpp
208+
struct Person {
209+
std::string first_name;
210+
std::string last_name;
211+
rfl::Description<"Must be a proper email in the form [email protected].",
212+
rfl::Email>
213+
email;
214+
rfl::Description<
215+
"The person's children. Pass an empty array for no children.",
216+
std::vector<Person>>
217+
children;
218+
float salary;
219+
};
220+
221+
const std::string json_schema = rfl::json::to_schema<Person>();
222+
```
223+
224+
The resulting JSON schema looks like this:
225+
226+
```json
227+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/definitions/Person","definitions":{"Person":{"type":"object","properties":{"children":{"type":"array","description":"The person's children. Pass an empty array for no children.","items":{"$ref":"#/definitions/Person"}},"email":{"type":"string","description":"Must be a proper email in the form [email protected].","pattern":"^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"},"first_name":{"type":"string"},"last_name":{"type":"string"},"salary":{"type":"number"}},"required":["children","email","first_name","last_name","salary"]}}}
228+
```
229+
230+
Note that this is currently supported for JSON only, since most other formats do not support schemata in the first place.
231+
203232
## Enums
204233

205234
reflect-cpp supports scoped enumerations:
@@ -391,6 +420,7 @@ reflect-cpp supports the following containers from the C++ standard library:
391420
- `std::unordered_set`
392421
- `std::variant`
393422
- `std::vector`
423+
- `std::wstring`
394424
395425
### Additional containers
396426

docs/README.md

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

3737
2.4) [Size validation](https://github.com/getml/reflect-cpp/blob/main/docs/size_validation.md) - For imposing size constraints on containers such as `std::vector` or `std::string`.
3838

39+
2.5) [JSON schema](https://github.com/getml/reflect-cpp/blob/main/docs/json_schema.md) - For validating your schema before you even send it to your C++ backend.
40+
3941
## 3) Custom classes
4042

4143
3.1) [Custom classes](https://github.com/getml/reflect-cpp/blob/main/docs/custom_classes.md) - For custom classes with private fields.

docs/json_schema.md

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# JSON schema
2+
3+
JSON schemata are a powerful tool for expressing the expected structure of your input. You can use it to validate your input before you even send it to your C++ backend, which will result in better UX.
4+
5+
It can also be used for code generation. For instance, tools such as https://app.quicktype.io/ allow you to generate code in multiple programming languages from the JSON schema (even though the validations are usually omitted).
6+
7+
If you are interacting with Python, we warmly recommend https://docs.pydantic.dev/latest/integrations/datamodel_code_generator/. This allows you to generate Pydantic dataclasses, including the validation, from the JSON schema.
8+
9+
Note that this is only supported for JSON, not for other formats.
10+
11+
## Basic idea
12+
13+
Suppose you have a struct like this:
14+
15+
```cpp
16+
struct Person {
17+
std::string first_name;
18+
std::string last_name;
19+
rfl::Email email;
20+
std::vector<Person> children;
21+
float salary;
22+
};
23+
```
24+
25+
You can generate a JSON schema like this:
26+
27+
```cpp
28+
const std::string json_schema = rfl::json::to_schema<Person>(rfl::json::pretty);
29+
```
30+
31+
You do not have to pass `rfl::json::pretty`, but for the purposes of this documentation it is better to do so.
32+
33+
This will result in the following JSON schema:
34+
35+
```json
36+
{
37+
"$schema": "https://json-schema.org/draft/2020-12/schema",
38+
"$ref": "#/definitions/Person",
39+
"definitions": {
40+
"Person": {
41+
"type": "object",
42+
"properties": {
43+
"children": {
44+
"type": "array",
45+
"items": {
46+
"$ref": "#/definitions/Person"
47+
}
48+
},
49+
"email": {
50+
"type": "string",
51+
"pattern": "^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"
52+
},
53+
"first_name": {
54+
"type": "string"
55+
},
56+
"last_name": {
57+
"type": "string"
58+
},
59+
"salary": {
60+
"type": "number"
61+
}
62+
},
63+
"required": [
64+
"children",
65+
"email",
66+
"first_name",
67+
"last_name",
68+
"salary"
69+
]
70+
}
71+
}
72+
}
73+
```
74+
75+
You can insert this into the tools mentioned above and see the generated code.
76+
77+
## Adding descriptions
78+
79+
JSON schemata also often contain descriptions. reflect-cpp supports this as well.
80+
81+
```cpp
82+
struct Person {
83+
std::string first_name;
84+
std::string last_name;
85+
rfl::Description<"Must be a proper email in the form [email protected].",
86+
rfl::Email>
87+
email;
88+
rfl::Description<
89+
"The person's children. Pass an empty array for no children.",
90+
std::vector<Person>>
91+
children;
92+
float salary;
93+
};
94+
```
95+
96+
```cpp
97+
const std::string json_schema = rfl::json::to_schema<Person>(rfl::json::pretty);
98+
```
99+
100+
```json
101+
{
102+
"$schema": "https://json-schema.org/draft/2020-12/schema",
103+
"$ref": "#/definitions/Person",
104+
"definitions": {
105+
"Person": {
106+
"type": "object",
107+
"properties": {
108+
"children": {
109+
"type": "array",
110+
"description": "The person's children. Pass an empty array for no children.",
111+
"items": {
112+
"$ref": "#/definitions/Person"
113+
}
114+
},
115+
"email": {
116+
"type": "string",
117+
"description": "Must be a proper email in the form [email protected].",
118+
"pattern": "^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"
119+
},
120+
"first_name": {
121+
"type": "string"
122+
},
123+
"last_name": {
124+
"type": "string"
125+
},
126+
"salary": {
127+
"type": "number"
128+
}
129+
},
130+
"required": [
131+
"children",
132+
"email",
133+
"first_name",
134+
"last_name",
135+
"salary"
136+
]
137+
}
138+
}
139+
}
140+
```
141+
142+
You also add a description to the entire JSON schema:
143+
144+
```cpp
145+
const std::string json_schema = rfl::json::to_schema<
146+
rfl::Description<"JSON schema that describes the required "
147+
"attributes for the person class.",
148+
Person>>(rfl::json::pretty);
149+
```
150+
151+
```json
152+
{
153+
"$schema": "https://json-schema.org/draft/2020-12/schema",
154+
"$ref": "#/definitions/Person",
155+
"description": "JSON schema that describes the required attributes for the person class.",
156+
"definitions": {
157+
"Person": {
158+
"type": "object",
159+
"properties": {
160+
"children": {
161+
"type": "array",
162+
"description": "The person's children. Pass an empty array for no children.",
163+
"items": {
164+
"$ref": "#/definitions/Person"
165+
}
166+
},
167+
"email": {
168+
"type": "string",
169+
"description": "Must be a proper email in the form [email protected].",
170+
"pattern": "^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"
171+
},
172+
"first_name": {
173+
"type": "string"
174+
},
175+
"last_name": {
176+
"type": "string"
177+
},
178+
"salary": {
179+
"type": "number"
180+
}
181+
},
182+
"required": [
183+
"children",
184+
"email",
185+
"first_name",
186+
"last_name",
187+
"salary"
188+
]
189+
}
190+
}
191+
}
192+
```

include/rfl/NamedTuple.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ class NamedTuple {
199199

200200
/// Invokes a callable object once for each field in order.
201201
template <typename F>
202-
void apply(const F& _f) {
202+
void apply(F&& _f) {
203203
const auto apply_to_field =
204204
[&_f]<typename... AFields>(AFields&&... fields) {
205205
((_f(std::forward<AFields>(fields))), ...);
@@ -209,7 +209,7 @@ class NamedTuple {
209209

210210
/// Invokes a callable object once for each field in order.
211211
template <typename F>
212-
void apply(const F& _f) const {
212+
void apply(F&& _f) const {
213213
const auto apply_to_field = [&_f](const auto&... fields) {
214214
((_f(fields)), ...);
215215
};
@@ -602,7 +602,7 @@ class NamedTuple<> {
602602

603603
/// Does nothing at all.
604604
template <typename F>
605-
void apply(const F& _f) const {}
605+
void apply(F&& _f) const {}
606606

607607
/// Returns an empty tuple.
608608
auto fields() const { return std::tuple(); }

tests/json/test_apply.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ void test() {
3838
}
3939
});
4040

41-
rfl::to_view(lisa).apply([](auto field) {
41+
rfl::to_view(lisa).apply([](auto field) mutable {
4242
if constexpr (decltype(field)::name() == "first_name") {
4343
*field.value() = "Bart";
4444
}

0 commit comments

Comments
 (0)