Skip to content

Commit 85f9642

Browse files
authored
[json schema] Use non-deprecated $defs; Add $comment option (#523)
1 parent 265b693 commit 85f9642

11 files changed

Lines changed: 42 additions & 34 deletions

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ const std::string json_schema = rfl::json::to_schema<Person>();
337337
The resulting JSON schema looks like this:
338338
339339
```json
340-
{"$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 xxx@xxx.xxx.","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"]}}}
340+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/$defs/Person","$defs":{"Person":{"type":"object","properties":{"children":{"type":"array","description":"The person's children. Pass an empty array for no children.","items":{"$ref":"#/$defs/Person"}},"email":{"type":"string","description":"Must be a proper email in the form xxx@xxx.xxx.","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"]}}}
341341
```
342342

343343
Note that this is currently supported for JSON only, since most other formats do not support schemata in the first place.

‎docs/json_schema.md‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ This will result in the following JSON schema:
3535
```json
3636
{
3737
"$schema": "https://json-schema.org/draft/2020-12/schema",
38-
"$ref": "#/definitions/Person",
39-
"definitions": {
38+
"$ref": "#/$defs/Person",
39+
"$defs": {
4040
"Person": {
4141
"type": "object",
4242
"properties": {
4343
"children": {
4444
"type": "array",
4545
"items": {
46-
"$ref": "#/definitions/Person"
46+
"$ref": "#/$defs/Person"
4747
}
4848
},
4949
"email": {
@@ -100,16 +100,16 @@ const std::string json_schema = rfl::json::to_schema<Person>(rfl::json::pretty);
100100
```json
101101
{
102102
"$schema": "https://json-schema.org/draft/2020-12/schema",
103-
"$ref": "#/definitions/Person",
104-
"definitions": {
103+
"$ref": "#/$defs/Person",
104+
"$defs": {
105105
"Person": {
106106
"type": "object",
107107
"properties": {
108108
"children": {
109109
"type": "array",
110110
"description": "The person's children. Pass an empty array for no children.",
111111
"items": {
112-
"$ref": "#/definitions/Person"
112+
"$ref": "#/$defs/Person"
113113
}
114114
},
115115
"email": {
@@ -151,17 +151,17 @@ const std::string json_schema = rfl::json::to_schema<
151151
```json
152152
{
153153
"$schema": "https://json-schema.org/draft/2020-12/schema",
154-
"$ref": "#/definitions/Person",
154+
"$ref": "#/$defs/Person",
155155
"description": "JSON schema that describes the required attributes for the person class.",
156-
"definitions": {
156+
"$defs": {
157157
"Person": {
158158
"type": "object",
159159
"properties": {
160160
"children": {
161161
"type": "array",
162162
"description": "The person's children. Pass an empty array for no children.",
163163
"items": {
164-
"$ref": "#/definitions/Person"
164+
"$ref": "#/$defs/Person"
165165
}
166166
},
167167
"email": {

‎include/rfl/json/schema/JSONSchema.hpp‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ template <class T>
1515
struct JSONSchema {
1616
Rename<"$schema", Literal<"https://json-schema.org/draft/2020-12/schema">>
1717
schema{};
18+
rfl::Rename<"$comment", std::optional<std::string>> comment{};
1819
Flatten<T> root{};
19-
std::map<std::string, Type> definitions{};
20+
rfl::Rename<"$defs", std::map<std::string, Type>> definitions{};
2021
};
2122

2223
} // namespace rfl::json::schema

‎include/rfl/json/to_schema.hpp‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@
99

1010
#include <string>
1111

12-
//#include "../Literal.hpp"
12+
// #include "../Literal.hpp"
1313
#include "../Processors.hpp"
1414
#include "../Variant.hpp"
15-
//#include "../parsing/schema/Type.hpp"
16-
//#include "../parsing/schema/ValidationType.hpp"
15+
// #include "../parsing/schema/Type.hpp"
16+
// #include "../parsing/schema/ValidationType.hpp"
1717
#include "../parsing/schema/make.hpp"
1818
#include "Reader.hpp"
1919
#include "Writer.hpp"
2020
#include "schema/JSONSchema.hpp"
21-
//#include "schema/Type.hpp"
22-
//#include "write.hpp"
21+
// #include "schema/Type.hpp"
22+
// #include "write.hpp"
2323

2424
namespace rfl::json {
2525

@@ -33,15 +33,16 @@ struct TypeHelper<rfl::Variant<Ts...>> {
3333

3434
std::string to_schema_internal_schema(
3535
const parsing::schema::Definition& internal_schema, const yyjson_write_flag,
36-
const bool _no_required);
36+
const bool _no_required, const std::string& comment = "");
3737

3838
/// Returns the JSON schema for a class.
3939
template <class T, class... Ps>
40-
std::string to_schema(const yyjson_write_flag _flag = 0) {
40+
std::string to_schema(const yyjson_write_flag _flag = 0,
41+
const std::string& comment = "") {
4142
using P = Processors<Ps...>;
4243
const auto internal_schema = parsing::schema::make<Reader, Writer, T, P>();
4344
return to_schema_internal_schema(internal_schema, _flag,
44-
P::default_if_missing_);
45+
P::default_if_missing_, comment);
4546
}
4647
} // namespace rfl::json
4748

‎src/rfl/json/to_schema.cpp‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ SOFTWARE.
2525
*/
2626

2727
#include "rfl/json/to_schema.hpp"
28+
2829
#include "rfl/json/schema/Type.hpp"
2930
#include "rfl/json/write.hpp"
3031

@@ -263,7 +264,7 @@ schema::Type type_to_json_schema_type(const parsing::schema::Type& _type,
263264

264265
} else if constexpr (std::is_same<T, Type::Reference>()) {
265266
return schema::Type{
266-
.value = schema::Type::Reference{.ref = "#/definitions/" + _t.name_}};
267+
.value = schema::Type::Reference{.ref = "#/$defs/" + _t.name_}};
267268

268269
} else if constexpr (std::is_same<T, Type::StringMap>()) {
269270
return schema::Type{
@@ -297,7 +298,8 @@ schema::Type type_to_json_schema_type(const parsing::schema::Type& _type,
297298

298299
std::string to_schema_internal_schema(
299300
const parsing::schema::Definition& internal_schema,
300-
const yyjson_write_flag _flag, const bool _no_required) {
301+
const yyjson_write_flag _flag, const bool _no_required,
302+
const std::string& comment) {
301303
auto definitions = std::map<std::string, schema::Type>();
302304
for (const auto& [k, v] : internal_schema.definitions_) {
303305
definitions[k] = type_to_json_schema_type(v, _no_required);
@@ -306,8 +308,12 @@ std::string to_schema_internal_schema(
306308
typename TypeHelper<schema::Type::ReflectionType>::JSONSchemaType;
307309
const auto to_schema = [&](auto&& _root) -> JSONSchemaType {
308310
using U = std::decay_t<decltype(_root)>;
309-
return schema::JSONSchema<U>{.root = std::move(_root),
310-
.definitions = definitions};
311+
return schema::JSONSchema<U>{
312+
.comment =
313+
!comment.empty() ? std::optional(std::move(comment)) : std::nullopt,
314+
.root = std::forward<decltype(_root)>(_root),
315+
.definitions = definitions,
316+
};
311317
};
312318
auto root = type_to_json_schema_type(internal_schema.root_, _no_required);
313319
const auto json_schema = rfl::visit(to_schema, std::move(root.value));

‎tests/json/test_json_schema.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ TEST(json, test_json_schema) {
6767
Person>>();
6868

6969
const std::string expected =
70-
R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/definitions/test_json_schema__Person","description":"JSON schema that describes the required attributes for the person class.","definitions":{"rfl__Generic":{"anyOf":[{"anyOf":[{"type":"boolean"},{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"object","additionalProperties":{"$ref":"#/definitions/rfl__Generic"}},{"type":"array","items":{"$ref":"#/definitions/rfl__Generic"}}]},{"type":"null"}]},"test_json_schema__Circle":{"type":"object","properties":{"radius":{"type":"number"}},"required":["radius"]},"test_json_schema__Circle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Circle"]},"radius":{"type":"number"}},"required":["shape","radius"]},"test_json_schema__Person":{"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","description":"Must be a proper email in the form xxx@xxx.xxx.","pattern":"^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"},"town":{"type":"string"},"color":{"type":"string","enum":["red","green","blue"]},"age":{"allOf":[{"minimum":0,"type":"integer"},{"maximum":130,"type":"integer"}]},"salary":{"type":"number"},"children":{"type":"array","description":"The person's children. Pass an empty array for no children.","items":{"$ref":"#/definitions/test_json_schema__Person"}},"variant":{"anyOf":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/definitions/test_json_schema__Person"}},{"type":"integer"}]},"tuple":{"type":"array","prefixItems":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/definitions/test_json_schema__Person"}},{"type":"integer"}],"items":false},"taggedUnion":{"anyOf":[{"$ref":"#/definitions/test_json_schema__Rectangle__tagged"},{"$ref":"#/definitions/test_json_schema__Square__tagged"},{"$ref":"#/definitions/test_json_schema__Circle__tagged"}]},"fieldVariant":{"anyOf":[{"type":"object","properties":{"rectangle":{"$ref":"#/definitions/test_json_schema__Rectangle"}},"required":["rectangle"]},{"type":"object","properties":{"square":{"$ref":"#/definitions/test_json_schema__Square"}},"required":["square"]},{"type":"object","properties":{"circle":{"$ref":"#/definitions/test_json_schema__Circle"}},"required":["circle"]}]},"generic":{"$ref":"#/definitions/rfl__Generic"},"with_extra_fields":{"$ref":"#/definitions/test_json_schema__WithExtraFields"}},"required":["firstName","lastName","email","town","color","age","salary","children","variant","tuple","taggedUnion","fieldVariant","generic","with_extra_fields"]},"test_json_schema__Rectangle":{"type":"object","properties":{"width":{"type":"number"},"height":{"type":"number"}},"required":["width","height"]},"test_json_schema__Rectangle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Rectangle"]},"width":{"type":"number"},"height":{"type":"number"}},"required":["shape","width","height"]},"test_json_schema__Square":{"type":"object","properties":{"width":{"type":"number"}},"required":["width"]},"test_json_schema__Square__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Square"]},"width":{"type":"number"}},"required":["shape","width"]},"test_json_schema__WithExtraFields":{"type":"object","properties":{"f1":{"type":"string"},"f2":{"type":"string"}},"required":["f1","f2"],"additionalProperties":{"type":"string"}}}})";
70+
R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/$defs/test_json_schema__Person","description":"JSON schema that describes the required attributes for the person class.","$defs":{"rfl__Generic":{"anyOf":[{"anyOf":[{"type":"boolean"},{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"object","additionalProperties":{"$ref":"#/$defs/rfl__Generic"}},{"type":"array","items":{"$ref":"#/$defs/rfl__Generic"}}]},{"type":"null"}]},"test_json_schema__Circle":{"type":"object","properties":{"radius":{"type":"number"}},"required":["radius"]},"test_json_schema__Circle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Circle"]},"radius":{"type":"number"}},"required":["shape","radius"]},"test_json_schema__Person":{"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","description":"Must be a proper email in the form xxx@xxx.xxx.","pattern":"^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"},"town":{"type":"string"},"color":{"type":"string","enum":["red","green","blue"]},"age":{"allOf":[{"minimum":0,"type":"integer"},{"maximum":130,"type":"integer"}]},"salary":{"type":"number"},"children":{"type":"array","description":"The person's children. Pass an empty array for no children.","items":{"$ref":"#/$defs/test_json_schema__Person"}},"variant":{"anyOf":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/$defs/test_json_schema__Person"}},{"type":"integer"}]},"tuple":{"type":"array","prefixItems":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/$defs/test_json_schema__Person"}},{"type":"integer"}],"items":false},"taggedUnion":{"anyOf":[{"$ref":"#/$defs/test_json_schema__Rectangle__tagged"},{"$ref":"#/$defs/test_json_schema__Square__tagged"},{"$ref":"#/$defs/test_json_schema__Circle__tagged"}]},"fieldVariant":{"anyOf":[{"type":"object","properties":{"rectangle":{"$ref":"#/$defs/test_json_schema__Rectangle"}},"required":["rectangle"]},{"type":"object","properties":{"square":{"$ref":"#/$defs/test_json_schema__Square"}},"required":["square"]},{"type":"object","properties":{"circle":{"$ref":"#/$defs/test_json_schema__Circle"}},"required":["circle"]}]},"generic":{"$ref":"#/$defs/rfl__Generic"},"with_extra_fields":{"$ref":"#/$defs/test_json_schema__WithExtraFields"}},"required":["firstName","lastName","email","town","color","age","salary","children","variant","tuple","taggedUnion","fieldVariant","generic","with_extra_fields"]},"test_json_schema__Rectangle":{"type":"object","properties":{"width":{"type":"number"},"height":{"type":"number"}},"required":["width","height"]},"test_json_schema__Rectangle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Rectangle"]},"width":{"type":"number"},"height":{"type":"number"}},"required":["shape","width","height"]},"test_json_schema__Square":{"type":"object","properties":{"width":{"type":"number"}},"required":["width"]},"test_json_schema__Square__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Square"]},"width":{"type":"number"}},"required":["shape","width"]},"test_json_schema__WithExtraFields":{"type":"object","properties":{"f1":{"type":"string"},"f2":{"type":"string"}},"required":["f1","f2"],"additionalProperties":{"type":"string"}}}})";
7171

7272
EXPECT_EQ(json_schema, expected);
7373
}

‎tests/json/test_json_schema2.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ TEST(json, test_json_schema2) {
6262
rfl::json::to_schema<DescribedType, rfl::SnakeCaseToCamelCase>();
6363

6464
const std::string expected =
65-
R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/definitions/test_json_schema2__Person","description":"JSON schema that describes the required attributes for the person class.","definitions":{"test_json_schema2__Circle":{"type":"object","properties":{"radius":{"type":"number"}},"required":["radius"]},"test_json_schema2__Circle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Circle"]},"radius":{"type":"number"}},"required":["shape","radius"]},"test_json_schema2__Person":{"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","description":"Must be a proper email in the form xxx@xxx.xxx.","pattern":"^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"},"town":{"type":"string"},"color":{"type":"string","enum":["red","green","blue"]},"age":{"allOf":[{"minimum":0,"type":"integer"},{"maximum":130,"type":"integer"}]},"salary":{"type":"number"},"children":{"type":"array","description":"The person's children. Pass an empty array for no children.","items":{"$ref":"#/definitions/test_json_schema2__Person"}},"variant":{"anyOf":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/definitions/test_json_schema2__Person"}},{"type":"integer"}]},"tuple":{"type":"array","prefixItems":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/definitions/test_json_schema2__Person"}},{"type":"integer"}],"items":false},"taggedUnion":{"anyOf":[{"$ref":"#/definitions/test_json_schema2__Rectangle__tagged"},{"$ref":"#/definitions/test_json_schema2__Square__tagged"},{"$ref":"#/definitions/test_json_schema2__Circle__tagged"}]},"fieldVariant":{"anyOf":[{"type":"object","properties":{"rectangle":{"$ref":"#/definitions/test_json_schema2__Rectangle"}},"required":["rectangle"]},{"type":"object","properties":{"square":{"$ref":"#/definitions/test_json_schema2__Square"}},"required":["square"]},{"type":"object","properties":{"circle":{"$ref":"#/definitions/test_json_schema2__Circle"}},"required":["circle"]}]}},"required":["firstName","lastName","email","town","color","age","salary","children","variant","tuple","taggedUnion","fieldVariant"]},"test_json_schema2__Rectangle":{"type":"object","properties":{"width":{"type":"number"},"height":{"type":"number"}},"required":["width","height"]},"test_json_schema2__Rectangle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Rectangle"]},"width":{"type":"number"},"height":{"type":"number"}},"required":["shape","width","height"]},"test_json_schema2__Square":{"type":"object","properties":{"width":{"type":"number"}},"required":["width"]},"test_json_schema2__Square__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Square"]},"width":{"type":"number"}},"required":["shape","width"]}}})";
65+
R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/$defs/test_json_schema2__Person","description":"JSON schema that describes the required attributes for the person class.","$defs":{"test_json_schema2__Circle":{"type":"object","properties":{"radius":{"type":"number"}},"required":["radius"]},"test_json_schema2__Circle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Circle"]},"radius":{"type":"number"}},"required":["shape","radius"]},"test_json_schema2__Person":{"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","description":"Must be a proper email in the form xxx@xxx.xxx.","pattern":"^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"},"town":{"type":"string"},"color":{"type":"string","enum":["red","green","blue"]},"age":{"allOf":[{"minimum":0,"type":"integer"},{"maximum":130,"type":"integer"}]},"salary":{"type":"number"},"children":{"type":"array","description":"The person's children. Pass an empty array for no children.","items":{"$ref":"#/$defs/test_json_schema2__Person"}},"variant":{"anyOf":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/$defs/test_json_schema2__Person"}},{"type":"integer"}]},"tuple":{"type":"array","prefixItems":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/$defs/test_json_schema2__Person"}},{"type":"integer"}],"items":false},"taggedUnion":{"anyOf":[{"$ref":"#/$defs/test_json_schema2__Rectangle__tagged"},{"$ref":"#/$defs/test_json_schema2__Square__tagged"},{"$ref":"#/$defs/test_json_schema2__Circle__tagged"}]},"fieldVariant":{"anyOf":[{"type":"object","properties":{"rectangle":{"$ref":"#/$defs/test_json_schema2__Rectangle"}},"required":["rectangle"]},{"type":"object","properties":{"square":{"$ref":"#/$defs/test_json_schema2__Square"}},"required":["square"]},{"type":"object","properties":{"circle":{"$ref":"#/$defs/test_json_schema2__Circle"}},"required":["circle"]}]}},"required":["firstName","lastName","email","town","color","age","salary","children","variant","tuple","taggedUnion","fieldVariant"]},"test_json_schema2__Rectangle":{"type":"object","properties":{"width":{"type":"number"},"height":{"type":"number"}},"required":["width","height"]},"test_json_schema2__Rectangle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Rectangle"]},"width":{"type":"number"},"height":{"type":"number"}},"required":["shape","width","height"]},"test_json_schema2__Square":{"type":"object","properties":{"width":{"type":"number"}},"required":["width"]},"test_json_schema2__Square__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Square"]},"width":{"type":"number"}},"required":["shape","width"]}}})";
6666

6767
EXPECT_EQ(json_schema, expected);
6868
}

0 commit comments

Comments
 (0)