Skip to content

Commit 5102b0b

Browse files
committed
Merge branch 'main' of github.com:geometryprocessing/simple-json-validator
2 parents b08d7e6 + 7cbb217 commit 5102b0b

File tree

4 files changed

+363
-36
lines changed

4 files changed

+363
-36
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"type": "cppdbg",
77
"request": "launch",
88
"program": "/home/daniele/git/simple-json-validator/build/tests/unit_tests",
9-
"args": ["type_string"],
9+
"args": [""],
1010
"stopAtEntry": false,
1111
"cwd": "/home/daniele/git/simple-json-validator/build/",
1212
"environment": [],

src/sjv/sjv.cpp

Lines changed: 176 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,85 @@
33
#include <iostream>
44
#include <filesystem> // C++17
55
#include <sstream>
6+
#include <algorithm>
67
////////////////////////////////////////////////////////////////////////////////
78

89
namespace sjv
910
{
11+
12+
//////////// PUBLIC
13+
1014
bool SJV::verify_json(const json &input, const json &rules)
1115
{
1216
log.clear();
1317
return verify_json("/", input, rules);
14-
}
18+
};
19+
20+
json SJV::inject_defaults(const json &input, const json &rules)
21+
{
22+
// The code below assumes that the input satisfies the rules
23+
assert(verify_json(input, rules));
24+
25+
// Find all the default rules
26+
json default_rules = collect_default_rules(rules);
27+
28+
// Flatten the input
29+
json flat = input.flatten();
30+
json out_flat = flat;
31+
32+
// For each rule, go over all entries of a flattened input
33+
for (auto rule : default_rules)
34+
{
35+
for (auto e : flat.items())
36+
{
37+
// If the pointer matches a strict subset of it, add it to the flattened
38+
std::tuple<bool, string> subset = is_subset_pointer(e.key(), string(rule["pointer"]));
39+
if (std::get<0>(subset))
40+
if (!out_flat.contains(std::get<1>(subset)))
41+
out_flat[std::get<1>(subset)] = rule["default"];
42+
43+
// Try it also without the last entry to capture object types which are empty
44+
string key_parent = e.key().substr(0,e.key().find_last_of("/\\"));
45+
46+
subset = is_subset_pointer(key_parent, string(rule["pointer"]));
47+
if (std::get<0>(subset))
48+
if (!out_flat.contains(std::get<1>(subset)))
49+
out_flat[std::get<1>(subset)] = rule["default"];
50+
51+
}
52+
// Special case as "/" is not inserted in the flat representation
53+
std::tuple<bool, string> subset = is_subset_pointer("/", string(rule["pointer"]));
54+
if (std::get<0>(subset))
55+
if (!out_flat.contains(std::get<1>(subset)))
56+
out_flat[std::get<1>(subset)] = rule["default"];
57+
}
58+
59+
// Unflatten the input
60+
json output = out_flat.unflatten();
61+
62+
// Certify the validity of the final file
63+
assert(verify_json(input, rules));
64+
65+
return output;
66+
};
67+
68+
std::string SJV::log2str()
69+
{
70+
std::stringstream s;
71+
72+
for (log_item i : log)
73+
{
74+
s << i.first << ": " << i.second << std::endl;
75+
}
76+
return s.str();
77+
};
78+
79+
//////////// PRIVATE
1580

1681
bool SJV::verify_json(const string &pointer, const json &input, const json &rules)
1782
{
83+
// if (pointer == "/geometry/*/surface_selection/*")
84+
// std::cout << "gotcha" << std::endl;
1885
// Find all rules that apply for the input node
1986
// TODO: accelerate this
2087
std::vector<json> matching_rules = collect_pointer(pointer, rules);
@@ -26,16 +93,24 @@ namespace sjv
2693

2794
if (strict)
2895
return false;
96+
else
97+
return true;
2998
}
3099

31100
// Test all rules, only one must pass, otherwise throw exception
32101
int count = 0;
102+
json single_matched_rule;
33103

34104
for (auto i : matching_rules)
105+
{
35106
if (verify_rule(input, i))
107+
{
36108
count++;
109+
single_matched_rule = i;
110+
}
111+
}
37112

38-
if (count == 0 && !matching_rules.empty())
113+
if (count == 0 && !matching_rules.empty())
39114
{
40115
// Before giving up, try boxing a primitive type
41116
if (boxing_primitive && !input.is_array())
@@ -44,7 +119,7 @@ namespace sjv
44119
// Make sure there are some rules for the boxed version before recursively checking
45120
if (collect_pointer(new_pointer, rules).size() > 0)
46121
if (verify_json(new_pointer, input, rules))
47-
return true;
122+
return true;
48123
}
49124

50125
std::stringstream s;
@@ -74,7 +149,7 @@ namespace sjv
74149
json defaults = collect_default_rules(new_pointer, rules);
75150

76151
// if it is mandatory, make sure there are no defaults
77-
if (matching_rules[0].contains("required") && contained_in_list(i.key(), matching_rules[0]["required"]))
152+
if (single_matched_rule.contains("required") && contained_in_list(i.key(), single_matched_rule["required"]))
78153
{
79154
if (defaults.size() != 0)
80155
{
@@ -83,7 +158,7 @@ namespace sjv
83158
}
84159
}
85160
// if it is optional, there should be only one default in the specs
86-
else if (matching_rules[0].contains("optional") && contained_in_list(i.key(), matching_rules[0]["optional"]))
161+
else if (single_matched_rule.contains("optional") && contained_in_list(i.key(), single_matched_rule["optional"]))
87162
{
88163
if (defaults.size() != 1)
89164
{
@@ -274,17 +349,21 @@ namespace sjv
274349
return false;
275350

276351
return true;
277-
}
352+
};
278353

279-
std::string SJV::log2str()
354+
json SJV::collect_default_rules(const json &rules)
280355
{
281-
std::stringstream s;
356+
// Find all rules that apply for the input node
357+
// TODO: accelerate this
282358

283-
for (log_item i : log)
359+
std::vector<json> matching_rules;
360+
for (auto i : rules)
284361
{
285-
s << i.first << ": " << i.second << std::endl;
362+
if (i.contains("default"))
363+
matching_rules.push_back(i);
286364
}
287-
return s.str();
365+
366+
return matching_rules;
288367
};
289368

290369
json SJV::collect_default_rules(const string &pointer, const json &rules)
@@ -307,7 +386,7 @@ namespace sjv
307386
return std::find(list.begin(), list.end(), item) != list.end();
308387
};
309388

310-
std::vector<json> SJV::collect_pointer(const string& pointer, const json& rules)
389+
std::vector<json> SJV::collect_pointer(const string &pointer, const json &rules)
311390
{
312391
std::vector<json> matching_rules;
313392
for (auto i : rules)
@@ -317,4 +396,89 @@ namespace sjv
317396
}
318397
return matching_rules;
319398
};
399+
400+
json SJV::find_valid_rule(const string &pointer, const json &input, const json &rules)
401+
{
402+
for (auto i : collect_pointer(pointer, rules))
403+
if (verify_rule(input, i))
404+
return i;
405+
406+
return json();
407+
};
408+
409+
std::tuple<bool, string> SJV::is_subset_pointer(const string &json, const string &pointer)
410+
{
411+
// Splits a string into tokens using the deliminator delim
412+
auto tokenize = [](std::string const &str, const char delim) {
413+
size_t start;
414+
size_t end = 0;
415+
std::vector<string> out;
416+
417+
while ((start = str.find_first_not_of(delim, end)) != std::string::npos)
418+
{
419+
end = str.find(delim, start);
420+
out.push_back(str.substr(start, end - start));
421+
}
422+
423+
return out;
424+
};
425+
426+
// Replaces occurrences of a substring
427+
auto replace_all = [](std::string str, const std::string &from, const std::string &to) {
428+
size_t start_pos = 0;
429+
while ((start_pos = str.find(from, start_pos)) != std::string::npos)
430+
{
431+
str.replace(start_pos, from.length(), to);
432+
start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
433+
}
434+
return str;
435+
};
436+
437+
// Check if a string is an integer
438+
auto is_number = [](const string &str) {
439+
for (char const &c : str)
440+
if (std::isdigit(c) == 0)
441+
return false;
442+
return true;
443+
};
444+
445+
// Tokenize json_pointer
446+
std::vector<string> json_t = tokenize(json, '/');
447+
std::vector<string> pointer_t = tokenize(pointer, '/');
448+
449+
// if the json is not shorter or if there are no tokens, give up
450+
if ((json_t.size() >= pointer_t.size())
451+
|| (pointer_t.size() == 0))
452+
return {false, ""};
453+
454+
std::string buf = "";
455+
// if it is shorter, match every entry
456+
for (unsigned i = 0; i < pointer_t.size(); ++i)
457+
{
458+
// if there is no corresponding entry on json, copy and move on
459+
if (json_t.size() <= i)
460+
{
461+
buf.append("/" + replace_all(pointer_t[i], "*", "0"));
462+
}
463+
// if there is an entry on json and it is the same, move on
464+
else if (json_t[i] == pointer_t[i])
465+
{
466+
buf.append("/" + pointer_t[i]);
467+
}
468+
// if the pointer contains a star, then accept any integer on json
469+
else if (pointer_t[i] == "*")
470+
{
471+
if (is_number(json_t[i]))
472+
buf.append("/" + json_t[i]);
473+
}
474+
// if no rule matches it is not a match
475+
else
476+
{
477+
return {false, ""};
478+
}
479+
}
480+
481+
return {true, buf};
482+
};
483+
320484
} // namespace sjv

src/sjv/sjv.h

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,11 @@ namespace sjv
1515
// verify the input json against the set of rules in specs
1616
bool verify_json(const json &input, const json &rules);
1717

18-
// Verify a node pointed by
19-
bool verify_json(const string &pointer, const json &input, const json &rules);
20-
21-
// Dispatcher for rule verification
22-
bool verify_rule(const json &input, const json &rule);
18+
// enriches a given json with default values
19+
json inject_defaults(const json &input, const json &rules);
2320

24-
// Type-specific rule handlers
25-
bool verify_rule_file(const json &input, const json &rule);
26-
bool verify_rule_folder(const json &input, const json &rule);
27-
bool verify_rule_float(const json &input, const json &rule);
28-
bool verify_rule_int(const json &input, const json &rule);
29-
bool verify_rule_string(const json &input, const json &rule);
30-
bool verify_rule_object(const json &input, const json &rule);
31-
bool verify_rule_bool(const json &input, const json &rule);
32-
bool verify_rule_list(const json &input, const json &rule);
33-
34-
// TODO
35-
json generate_default_json(const json &rules);
21+
// log to string
22+
std::string log2str();
3623

3724
// Working directory
3825
string cwd = ".";
@@ -48,17 +35,44 @@ namespace sjv
4835
typedef std::pair<std::string, std::string> log_item;
4936
std::vector<log_item> log;
5037

51-
// log to string
52-
std::string log2str();
38+
private:
39+
// enriches a given json with default values
40+
void inject_defaults(const string &pointer, const json &input, const json &rules, json &output);
41+
42+
// Verify a node pointed by
43+
bool verify_json(const string &pointer, const json &input, const json &rules);
44+
45+
// Dispatcher for rule verification
46+
bool verify_rule(const json &input, const json &rule);
47+
48+
// Type-specific rule handlers
49+
bool verify_rule_file(const json &input, const json &rule);
50+
bool verify_rule_folder(const json &input, const json &rule);
51+
bool verify_rule_float(const json &input, const json &rule);
52+
bool verify_rule_int(const json &input, const json &rule);
53+
bool verify_rule_string(const json &input, const json &rule);
54+
bool verify_rule_object(const json &input, const json &rule);
55+
bool verify_rule_bool(const json &input, const json &rule);
56+
bool verify_rule_list(const json &input, const json &rule);
57+
58+
// Collect all rules having a default
59+
json collect_default_rules(const json &rules);
5360

5461
// Collect all rules having a default for a given pointer
5562
json collect_default_rules(const string &pointer, const json &rules);
5663

5764
// Collect all rules having a given pointer
5865
std::vector<json> collect_pointer(const string &pointer, const json &rules);
5966

67+
// Find the first rule matching a pointer
68+
json find_valid_rule(const string &pointer, const json &input, const json &rules);
69+
6070
// Utils
6171
bool contained_in_list(string item, const json &list);
72+
73+
// Checks if a given json pointer is a subset of a pointer string (containing wildcards).
74+
// If it is, the second return parameter is an instantiated pointer
75+
std::tuple<bool, string> is_subset_pointer(const string &json_pointer, const string &pointer);
6276
};
6377

6478
} // namespace sjv

0 commit comments

Comments
 (0)