C++库json-schema-validator源码分析

最近框架接json schema的需求,因此对其进行了调研

star最多的两个库https://github.com/pboettch/json-schema-validator和https://github.com/danielaparker/jsoncons(已完成)

json-schema-validator jsoncons
规范支持 Draft 7 Draft 7,Draft 2019-09,Draft 2020-12
字符串格式检查器 没有预设,都要自己实现 支持date,email,tcp等常见的数十种
外部依赖 C++11起,依赖github.com/nlohmann/json C++20起
更新频率 253 commits,7 months ago 12335 commits,yesterday

总体而言,jsoncons会更好(规范支持全面,功能多,更新频率高),但是接入难度更高(依赖C++20,框架要兼容C++11的钉子户用户)

json-schema-validator基本功能都有,凑活够用,所以还是先接入json-schema-validator,对其进行源码分析

这实际上也是对Draft 7标准的一个学习过程

分析是基于commit id 40af3ec39670e768fc3f01f935140af311d71024进行的

debug代码

由于存在很多递归调用,为了更好地分析代码运行流程,加一些dump堆栈的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#pragma once
#define UNW_LOCAL_ONLY
#include <libunwind.h>

#include <string>
#include <cstring>

#include <cxxabi.h>
#include <link.h>

inline size_t ConvertToVMA(size_t addr)
{
Dl_info info;
link_map* link_map;
dladdr1((void*)addr,&info,(void**)&link_map,RTLD_DL_LINKMAP);
return addr-link_map->l_addr;
}

inline std::string getFileAndLine(size_t addr, const char* binary_path) {
// addr2line 本身不能去除长长一串的参数,因此只能交给c++filt -p来去除参数
// 输出会是两行,awk直接读两行再拼接at
// 例如:
// ~ addr2line -e ./a.out -f 0x61b06 | c++filt -p | awk 'NR==1{fn=$0;next}{print fn " at " $0}'
// 输出(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390
char addr2line_cmd[1024];
snprintf(
addr2line_cmd, sizeof(addr2line_cmd),
R"(addr2line -e %s -f %lx|c++filt -p|awk 'NR==1{fn=$0;next}{print fn " at " $0}')",
binary_path, addr);

FILE* pipe = popen(addr2line_cmd, "r");
if (!pipe) {
return "??:??"; // 无法解析时返回占位符
}

char addr2line_output[8 * 1024];
if (fgets(addr2line_output, sizeof(addr2line_output), pipe)) {
// 移除末尾的换行符
size_t len = strlen(addr2line_output);
if (len > 0 && addr2line_output[len - 1] == '\n') {
addr2line_output[len - 1] = '\0';
}
pclose(pipe);
return std::string(addr2line_output);
}

pclose(pipe);
return "??:??"; // 无法解析时返回占位符
}

inline std::string getBacktrace() {
unw_cursor_t cursor;
unw_context_t uc;
unw_word_t ip;
unw_getcontext(&uc); // 保存当前寄存器上下文
unw_init_local(&cursor, &uc); // 初始化游标

std::string s("\n");

while (unw_step(&cursor) > 0) { // 遍历栈帧
unw_get_reg(&cursor, UNW_REG_IP, &ip); // 获取指令指针寄存器 (IP)

// 使用 dladdr 获取二进制文件路径
Dl_info info;
std::string file_and_line = "??:??";
if (dladdr((void*)ip, &info) && info.dli_fname) {
size_t vma_ip = ConvertToVMA(ip); // 转换为虚拟地址
file_and_line = getFileAndLine(vma_ip, info.dli_fname); // 获取文件名和行号
}
s += (file_and_line + '\n');
}

return s;
}

然后是核心创建schema的代码,在创建开始的时候打印start和选中的json字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
std::shared_ptr<schema> schema::make(json &schema,
root_schema *root,
const std::vector<std::string> &keys,
std::vector<nlohmann::json_uri> uris) {
std::string keysStr;
for (auto &key : keys) {
keysStr += key + "|";
}
std::string urisStr;
for (auto &uri : uris) {
urisStr += uri.to_string() + "|";
}
for (auto uri = uris.begin(); uri != uris.end();)
if (uri->identifier() != "")
uri = uris.erase(uri);
else
uri++;

// append to all URIs the keys for this sub-schema
for (auto &key : keys)
for (auto &uri : uris)
uri = uri.append(key);

std::string newUrisStr;
for (auto &uri : uris) {
newUrisStr += uri.to_string() + "|";
}
auto oldSchema = schema;
std::cout << "============================================" << std::endl <<
"start|" << oldSchema.items() << std::endl <<
"|keys=" << keysStr << "|uris=" << urisStr <<
"|newUrisStr=" << newUrisStr << "|backTrace=" << getBacktrace() << std::endl;

...

for (auto &uri : uris) { // for all URIs this schema is referenced by
std::cout << oldSchema.items() << "insert uri=" << uri.to_string() << std::endl;
root->insert(uri, sch);

if (schema.type() == json::value_t::object)
for (auto &u : schema.items())
root->insert_unknown_keyword(uri, u.key(), u.value()); // insert unknown keywords for later reference
}
std::cout <<
"end|" << oldSchema.items() << std::endl <<
"============================================" << std::endl;
}

测试代码

测试的case如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <nlohmann/json.hpp>
#include <nlohmann/json-schema.hpp>
#include <iostream>
#include <regex>

using nlohmann::json;
using nlohmann::json_schema::json_validator;

// 自定义格式检查器函数
void format_checker(const std::string &format, const std::string &value) {
if (format == "email") {
// 使用正则表达式检查 email 格式
const std::regex email_regex(R"((\w+)(\.\w+)*@(\w+)(\.\w+)+)");
if (!std::regex_match(value, email_regex)) {
throw std::invalid_argument("Invalid email format: " + value);
}
} else if (format == "date-time") {
// 使用正则表达式检查 date-time 格式 (ISO 8601)
const std::regex datetime_regex(R"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)");
if (!std::regex_match(value, datetime_regex)) {
throw std::invalid_argument("Invalid date-time format: " + value);
}
} else {
throw std::invalid_argument("Unsupported format: " + format);
}
}

int main() {
// 定义 JSON Schema
static json person_schema = R"(
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person Schema",
"type": "object",
"definitions": {
"name": {
"type": "string",
"description": "The person's name"
},
"contact": {
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" },
"phone": { "type": "string" }
},
"required": ["email"]
}
},
"properties": {
"user": {
"type": "object",
"properties": {
"name": { "$ref": "#/definitions/name" },
"contact": { "$ref": "#/definitions/contact" }
},
"required": ["name", "contact"]
},
"admin": {
"type": "object",
"properties": {
"name": { "$ref": "#/definitions/name" },
"contact": { "$ref": "#/definitions/contact" },
"role": { "type": "string" }
},
"required": ["name", "contact", "role"]
},
"guest": {
"type": "object",
"properties": {
"name": { "$ref": "#/definitions/name" },
"contact": { "$ref": "#/definitions/contact" },
"visit_time": { "type": "string", "format": "date-time" }
},
"required": ["name", "contact", "visit_time"]
}
}
}
)"_json;

// 定义待验证的 JSON 数据
json person_data = R"(
{
"user": {
"name": "Alice",
"contact": {
"email": "alice@example.com",
"phone": "123-456-7890"
}
},
"admin": {
"name": "Bob",
"contact": {
"email": "bob@example.com",
"phone": "987-654-3210"
},
"role": "superadmin"
},
"guest": {
"name": "Charlie",
"contact": {
"email": "charlie@example.com",
"phone": "555-555-5555"
},
"visit_time": "2023-10-01T12:00:00Z"
}
}
)"_json;

// 验证 JSON 数据
try {
json_validator validator(nullptr, format_checker);
validator.set_root_schema(person_schema); // 设置 Schema
validator.validate(person_data); // 验证数据
std::cout << "Validation passed!" << std::endl;
} catch (const std::exception &e) {
std::cerr << "Validation failed: " << e.what() << std::endl;
}

return 0;
}

编译

1
g++ -g -Wall -I ../tmp_install/include/ demo.cpp ../tmp_install/lib/libnlohmann_json_schema_validator.a -lunwind

测试输出

输出去除了一些无用的堆栈内容

顶层person_schema

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~ /a.out |grep -v 'shared_ptr\|c++/13\|libc-start\|_start'
============================================
start|[{"$schema":"http://json-schema.org/draft-07/schema#"},{"definitions":{"contact":{"properties":{"email":{"format":"email","type":"string"},"phone":{"type":"string"}},"required":["email"],"type":"object"},"name":{"description":"The person's name","type":"string"}}},{"properties":{"admin":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"role":{"type":"string"}},"required":["name","contact","role"],"type":"object"},"guest":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"visit_time":{"format":"date-time","type":"string"}},"required":["name","contact","visit_time"],"type":"object"},"user":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"}},"required":["name","contact"],"type":"object"}}},{"title":"Person Schema"},{"type":"object"}]
|keys=|uris= # ||newUrisStr= # ||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

...

[{"$schema":"http://json-schema.org/draft-07/schema#"},{"definitions":{"contact":{"properties":{"email":{"format":"email","type":"string"},"phone":{"type":"string"}},"required":["email"],"type":"object"},"name":{"description":"The person's name","type":"string"}}},{"properties":{"admin":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"role":{"type":"string"}},"required":["name","contact","role"],"type":"object"},"guest":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"visit_time":{"format":"date-time","type":"string"}},"required":["name","contact","visit_time"],"type":"object"},"user":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"}},"required":["name","contact"],"type":"object"}}},{"title":"Person Schema"},{"type":"object"}]insert uri= #

end|[{"$schema":"http://json-schema.org/draft-07/schema#"},{"definitions":{"contact":{"properties":{"email":{"format":"email","type":"string"},"phone":{"type":"string"}},"required":["email"],"type":"object"},"name":{"description":"The person's name","type":"string"}}},{"properties":{"admin":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"role":{"type":"string"}},"required":["name","contact","role"],"type":"object"},"guest":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"visit_time":{"format":"date-time","type":"string"}},"required":["name","contact","visit_time"],"type":"object"},"user":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"}},"required":["name","contact"],"type":"object"}}},{"title":"Person Schema"},{"type":"object"}]
============================================
Validation passed!

首先从main函数的set_root_schema开始,进入schema::make来创建顶层person_schema

会命中如下逻辑,也就是查找schema下面是否存在定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::shared_ptr<schema> schema::make(json &schema,
root_schema *root,
const std::vector<std::string> &keys,
std::vector<nlohmann::json_uri> uris) {
auto findDefinitions = [&](const std::string &defs) -> bool {
attr = schema.find(defs);
if (attr != schema.end()) {
for (auto &def : attr.value().items())
schema::make(def.value(), root, {defs, def.key()}, uris);
schema.erase(attr);
return true;
}
return false;
};
if (!findDefinitions("$defs")) {
findDefinitions("definitions");
}
}

schema关键词definitions

每个定义的创建都是schema::make(def.value(), root, {defs, def.key()}, uris);

contact
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
============================================
start|[{"properties":{"email":{"format":"email","type":"string"},"phone":{"type":"string"}}},{"required":["email"]},{"type":"object"}]
|keys=definitions|contact||uris= # ||newUrisStr= # /definitions/contact||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::schema::make(nlohmann::json_abi_v3_11_2::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_2::adl_serializer, std::vector<unsigned char, std::allocator<unsigned char> > >&, nlohmann::json_schema::root_schema*, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, std::vector<nlohmann::json_uri, std::allocator<nlohmann::json_uri> >)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)#1}::operator() at /root/json-schema-validator/src/json-validator.cpp:1414 (discriminator 8)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1421 (discriminator 4)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

============================================
start|[{"format":"email"},{"type":"string"}]
|keys=properties|email||uris= # /definitions/contact||newUrisStr= # /definitions/contact/properties/email||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
(anonymous namespace)::schema::make(nlohmann::json_abi_v3_11_2::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_2::adl_serializer, std::vector<unsigned char, std::allocator<unsigned char> > >&, nlohmann::json_schema::root_schema*, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, std::vector<nlohmann::json_uri, std::allocator<nlohmann::json_uri> >)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)#1}::operator() at /root/json-schema-validator/src/json-validator.cpp:1414 (discriminator 8)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1421 (discriminator 4)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

[{"format":"email"},{"type":"string"}]insert uri= # /definitions/contact/properties/email
end|[{"format":"email"},{"type":"string"}]
============================================
============================================
start|[{"type":"string"}]
|keys=properties|phone||uris= # /definitions/contact||newUrisStr= # /definitions/contact/properties/phone||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
(anonymous namespace)::schema::make(nlohmann::json_abi_v3_11_2::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_2::adl_serializer, std::vector<unsigned char, std::allocator<unsigned char> > >&, nlohmann::json_schema::root_schema*, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, std::vector<nlohmann::json_uri, std::allocator<nlohmann::json_uri> >)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)#1}::operator() at /root/json-schema-validator/src/json-validator.cpp:1414 (discriminator 8)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1421 (discriminator 4)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

[{"type":"string"}]insert uri= # /definitions/contact/properties/phone
end|[{"type":"string"}]
============================================
[{"properties":{"email":{"format":"email","type":"string"},"phone":{"type":"string"}}},{"required":["email"]},{"type":"object"}]insert uri= # /definitions/contact
end|[{"properties":{"email":{"format":"email","type":"string"},"phone":{"type":"string"}}},{"required":["email"]},{"type":"object"}]

对于定义contract,schema::make后进入了

(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)

对应代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
std::shared_ptr<schema> schema::make(json &schema,
root_schema *root,
const std::vector<std::string> &keys,
std::vector<nlohmann::json_uri> uris)
{
if (schema.type() == json::value_t::boolean)
sch = std::make_shared<boolean>(schema, root);
else if (schema.type() == json::value_t::object) {
attr = schema.find("$ref");
if (attr != schema.end()) {
...
}else {
sch = std::make_shared<type_schema>(schema, root, uris);
}
}
...
}

也就是说,如果schema::make的类型不是引用类型(找不到$ref),就type_schema::make创建type_schema类型的schema进行分析

type_schema::make又会进入schema::make逻辑,这是因为如下代码会对properties又创建一个schema进行分析:

1
2
3
4
5
6
7
8
9
10
//在object的构造函数(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
attr = sch.find("properties");
if (attr != sch.end()) {
for (auto prop : attr.value().items())
properties_.insert(
std::make_pair(
prop.key(),
schema::make(prop.value(), root, {"properties", prop.key()}, uris)));
sch.erase(attr);
}
name

输出更简单,因为没有properties类型,不会再创建schema

1
2
3
4
5
6
7
8
9
10
11
12
13
============================================
start|[{"description":"The person's name"},{"type":"string"}]
|keys=definitions|name||uris= # ||newUrisStr= # /definitions/name||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::schema::make(nlohmann::json_abi_v3_11_2::basic_json<std::map, std::vector, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, long, unsigned long, double, std::allocator, nlohmann::json_abi_v3_11_2::adl_serializer, std::vector<unsigned char, std::allocator<unsigned char> > >&, nlohmann::json_schema::root_schema*, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, std::vector<nlohmann::json_uri, std::allocator<nlohmann::json_uri> >)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)#1}::operator() at /root/json-schema-validator/src/json-validator.cpp:1414 (discriminator 8)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1421 (discriminator 4)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

[{"description":"The person's name"},{"type":"string"}]insert uri= # /definitions/name
end|[{"description":"The person's name"},{"type":"string"}]
============================================

schema关键词properties

现在要分析顶层person_schema下的每个properties,正如上文所说

如果schema::make的类型不是引用类型(找不到$ref),就type_schema::make创建type_schema类型的schema进行分析

type_schema::make又会进入schema::make逻辑,这是因为如下代码会对properties又创建一个schema进行分析:

1
2
3
4
5
6
7
8
9
10
//在object的构造函数(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
attr = sch.find("properties");
if (attr != sch.end()) {
for (auto prop : attr.value().items())
properties_.insert(
std::make_pair(
prop.key(),
schema::make(prop.value(), root, {"properties", prop.key()}, uris)));
sch.erase(attr);
}

顶层person_schema下存在3个properties

user

person_schema/properties/user作为type_schema创建以后,又触发了它的properties创建

也就是person_schema/properties/user/properties/contactperson_schema/properties/user/properties/name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
============================================
start|[{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"}}},{"required":["name","contact"]},{"type":"object"}]
|keys=properties|user||uris= # ||newUrisStr= # /properties/user||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

============================================
start|[{"$ref":"#/definitions/contact"}]
|keys=properties|contact||uris= # /properties/user||newUrisStr= # /properties/user/properties/contact||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

[{"$ref":"#/definitions/contact"}]insert uri= # /properties/user/properties/contact
end|[{"$ref":"#/definitions/contact"}]
============================================
============================================
start|[{"$ref":"#/definitions/name"}]
|keys=properties|name||uris= # /properties/user||newUrisStr= # /properties/user/properties/name||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

[{"$ref":"#/definitions/name"}]insert uri= # /properties/user/properties/name
end|[{"$ref":"#/definitions/name"}]
============================================
[{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"}}},{"required":["name","contact"]},{"type":"object"}]insert uri= # /properties/user
end|[{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"}}},{"required":["name","contact"]},{"type":"object"}]

每个user的子properties都有一个类似的堆栈

1
2
3
4
5
6
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145

这就是user的构造函数,对每个子properties都创建了一个type_schema,最后调用到schema::make

schema关键词ref

根据$ref引用已经存在的url元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
std::shared_ptr<schema> schema::make(json &schema,
root_schema *root,
const std::vector<std::string> &keys,
std::vector<nlohmann::json_uri> uris)
{
if (schema.type() == json::value_t::boolean)
sch = std::make_shared<boolean>(schema, root);
else if (schema.type() == json::value_t::object) {
attr = schema.find("$ref");
if (attr != schema.end()) {
auto id = uris.back().derive(attr.value().get<std::string>());
//去uris里面找到已经存在的,或者创建一个新的对象(等待后续实际的definitions去创建)
sch = root->get_or_create_ref(id);
}else {
//否则在uris里面创建新的
sch = std::make_shared<type_schema>(schema, root, uris);
}
}
for (auto &uri : uris) {
root->insert(uri, sch);
}
}

不管是root->get_or_create_ref(),还是root->insert(),最后实际上都是调用了root->get_or_create_file()去在root的files_全局变量里面插入新的file

1
2
3
4
5
6
7
8
schema_file &get_or_create_file(const std::string &loc)
{
auto file = files_.lower_bound(loc);
if (file != files_.end() && !(files_.key_comp()(loc, file->first)))
return file->second;
else
return files_.insert(file, {loc, {}})->second;
}

这就意味着root->insert(uri, root->get_or_create_ref(id)),这样插入一个空的占着位置也没事

后续解析到这个ref对应的uri,还会再插入一次root->insert(uri, std::make_shared<type_schema>(schema, root, uris))去覆盖

如果main函数里面validate的时候,还没有实际的type_schema,那就可以报错了

admin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
start|[{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"role":{"type":"string"}}},{"required":["name","contact","role"]},{"type":"object"}]
|keys=properties|admin||uris= # ||newUrisStr= # /properties/admin||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

============================================
start|[{"$ref":"#/definitions/contact"}]
|keys=properties|contact||uris= # /properties/admin||newUrisStr= # /properties/admin/properties/contact||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

[{"$ref":"#/definitions/contact"}]insert uri= # /properties/admin/properties/contact
end|[{"$ref":"#/definitions/contact"}]
============================================
============================================
start|[{"$ref":"#/definitions/name"}]
|keys=properties|name||uris= # /properties/admin||newUrisStr= # /properties/admin/properties/name||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

[{"$ref":"#/definitions/name"}]insert uri= # /properties/admin/properties/name
end|[{"$ref":"#/definitions/name"}]
============================================
============================================
start|[{"type":"string"}]
|keys=properties|role||uris= # /properties/admin||newUrisStr= # /properties/admin/properties/role||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

[{"type":"string"}]insert uri= # /properties/admin/properties/role
end|[{"type":"string"}]
============================================
[{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"role":{"type":"string"}}},{"required":["name","contact","role"]},{"type":"object"}]insert uri= # /properties/admin
end|[{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"role":{"type":"string"}}},{"required":["name","contact","role"]},{"type":"object"}]
guest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
============================================
start|[{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"visit_time":{"format":"date-time","type":"string"}}},{"required":["name","contact","visit_time"]},{"type":"object"}]
|keys=properties|guest||uris= # ||newUrisStr= # /properties/guest||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

============================================
start|[{"$ref":"#/definitions/contact"}]
|keys=properties|contact||uris= # /properties/guest||newUrisStr= # /properties/guest/properties/contact||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

[{"$ref":"#/definitions/contact"}]insert uri= # /properties/guest/properties/contact
end|[{"$ref":"#/definitions/contact"}]
============================================
============================================
start|[{"$ref":"#/definitions/name"}]
|keys=properties|name||uris= # /properties/guest||newUrisStr= # /properties/guest/properties/name||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

[{"$ref":"#/definitions/name"}]insert uri= # /properties/guest/properties/name
end|[{"$ref":"#/definitions/name"}]
============================================
============================================
start|[{"format":"date-time"},{"type":"string"}]
|keys=properties|visit_time||uris= # /properties/guest||newUrisStr= # /properties/guest/properties/visit_time||backTrace=
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
(anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
(anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
(anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
(anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1443 (discriminator 2)
nlohmann::json_schema::root_schema::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:283 (discriminator 4)
nlohmann::json_schema::json_validator::set_root_schema at /root/json-schema-validator/src/json-validator.cpp:1522 (discriminator 4)
main at /root/json-schema-validator/demo/demo1.cpp:113

[{"format":"date-time"},{"type":"string"}]insert uri= # /properties/guest/properties/visit_time
end|[{"format":"date-time"},{"type":"string"}]
============================================
[{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"visit_time":{"format":"date-time","type":"string"}}},{"required":["name","contact","visit_time"]},{"type":"object"}]insert uri= # /properties/guest
end|[{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"visit_time":{"format":"date-time","type":"string"}}},{"required":["name","contact","visit_time"]},{"type":"object"}]

至此,整个person_schema解析完毕

解析完毕

输出,就是一开始顶层person_schema的部分内容,将整个person_schema插入到顶层uri#

1
2
3
4
5
[{"$schema":"http://json-schema.org/draft-07/schema#"},{"definitions":{"contact":{"properties":{"email":{"format":"email","type":"string"},"phone":{"type":"string"}},"required":["email"],"type":"object"},"name":{"description":"The person's name","type":"string"}}},{"properties":{"admin":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"role":{"type":"string"}},"required":["name","contact","role"],"type":"object"},"guest":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"visit_time":{"format":"date-time","type":"string"}},"required":["name","contact","visit_time"],"type":"object"},"user":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"}},"required":["name","contact"],"type":"object"}}},{"title":"Person Schema"},{"type":"object"}]insert uri= # 

end|[{"$schema":"http://json-schema.org/draft-07/schema#"},{"definitions":{"contact":{"properties":{"email":{"format":"email","type":"string"},"phone":{"type":"string"}},"required":["email"],"type":"object"},"name":{"description":"The person's name","type":"string"}}},{"properties":{"admin":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"role":{"type":"string"}},"required":["name","contact","role"],"type":"object"},"guest":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"},"visit_time":{"format":"date-time","type":"string"}},"required":["name","contact","visit_time"],"type":"object"},"user":{"properties":{"contact":{"$ref":"#/definitions/contact"},"name":{"$ref":"#/definitions/name"}},"required":["name","contact"],"type":"object"}}},{"title":"Person Schema"},{"type":"object"}]
============================================
Validation passed!

总结

这一篇分析比较粗糙,主要是跟着一个实际的解析例子走了一遍流程

递归流程虽然看起来复杂,实际上是有规律可循的:

  • definitions流程

    在root的schema下的schema::make中,创建出对应url的schema,给下一个properties流程引用

  • properties流程

    每个type object子字段的,都是会通过type_schema::make去创建出一个object

    凡是object,会对properties再递归调用type_schema::make去创建出一个object,也就是

    1
    2
    3
    4
    5
    (anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145
    (anonymous namespace)::type_schema::make at /root/json-schema-validator/src/json-validator.cpp:1338 (discriminator 2)
    (anonymous namespace)::type_schema::type_schema at /root/json-schema-validator/src/json-validator.cpp:638 (discriminator 2)
    (anonymous namespace)::schema::make at /root/json-schema-validator/src/json-validator.cpp:1390 (discriminator 9)
    (anonymous namespace)::object::object at /root/json-schema-validator/src/json-validator.cpp:1145

    所以root object会递归创建第一级的properties的各个object

    而这每个object,又会对第二级properties再递归创建object

    直到最低层级的叶子节点,变成了例如"type": "string", "format": "email"的内容

    那么就不再创建object了,而是对其format去进行分析了

    flowchart LR
        %% ───────── 入口 ─────────
        RootMake["schema::make (root)"] --> RootObj["object::object (root)"]
    
        %% ───────── 第 1 级 ─────────
        P1{{"properties\n(第1级)"}}
        RootObj --> P1
        P1 -- 每个 key --> Obj1["object::object (第1级)"]
    
        %% ───────── 继续递归 / 叶子判定 ─────────
        IsLeaf{"是否叶子?<br>(type is object?)"}
        Obj1 --> IsLeaf
        IsLeaf -- 否 --> Pn{{"properties\n(...)"}} 
        IsLeaf -- 是 --> Leaf["leaf node:\n type=string\n format=email"]