Use json patch format for default
json patch (RFC 6902) makes it clearer in case of array contents. It is supported by nlohmann::json and can be applied with `patch` to fill up the json with defaults.
This commit is contained in:
parent
75853c11ee
commit
6f3005dc75
@ -13,6 +13,7 @@ add_library(nlohmann_json_schema_validator
|
|||||||
src/json-schema-draft7.json.cpp
|
src/json-schema-draft7.json.cpp
|
||||||
src/json-uri.cpp
|
src/json-uri.cpp
|
||||||
src/json-validator.cpp
|
src/json-validator.cpp
|
||||||
|
src/json_patch.cpp
|
||||||
src/string-format-check.cpp)
|
src/string-format-check.cpp)
|
||||||
|
|
||||||
target_include_directories(nlohmann_json_schema_validator
|
target_include_directories(nlohmann_json_schema_validator
|
||||||
|
|||||||
@ -10,41 +10,65 @@ using nlohmann::json_schema::json_validator;
|
|||||||
static json person_schema = R"(
|
static json person_schema = R"(
|
||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "A person",
|
"title": "Dict",
|
||||||
"properties": {
|
"type": "array",
|
||||||
"name": {
|
"items": {
|
||||||
"description": "Name",
|
"title": "A person",
|
||||||
"type": "string"
|
"properties": {
|
||||||
},
|
"name": {
|
||||||
"age": {
|
"description": "Name",
|
||||||
"description": "Age of the person",
|
"type": "string"
|
||||||
"type": "number",
|
},
|
||||||
"minimum": 2,
|
"age": {
|
||||||
"maximum": 200
|
"description": "Age of the person",
|
||||||
},
|
"type": "number",
|
||||||
"address":{
|
"minimum": 2,
|
||||||
"type": "object",
|
"maximum": 200
|
||||||
"properties":{
|
},
|
||||||
"street":{
|
"address":{
|
||||||
"type": "string",
|
"type": "object",
|
||||||
"default": "Abbey Road"
|
"properties":{
|
||||||
|
"street":{
|
||||||
|
"type": "string",
|
||||||
|
"default": "Abbey Road"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
"required": [
|
||||||
"required": [
|
"name",
|
||||||
"name",
|
"age"
|
||||||
"age"
|
],
|
||||||
],
|
"type": "object"
|
||||||
"type": "object"
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
)"_json;
|
)"_json;
|
||||||
|
|
||||||
// The people are defined with brace initialization
|
// The people are defined with brace initialization
|
||||||
static json bad_person = {{"age", 42}};
|
static json bad_person = R"([
|
||||||
static json good_person = {{"name", "Albert"}, {"age", 42}, {"address", {{"street", "Main Street"}}}};
|
{"age": 42}
|
||||||
static json good_defaulted_person = {{"name", "Knut"}, {"age", 69}, {"address", {}}};
|
])"_json;
|
||||||
|
|
||||||
|
static json good_person = R"([
|
||||||
|
{
|
||||||
|
"name": "Albert",
|
||||||
|
"age": 42,
|
||||||
|
"address": {"street": "Main Street"}
|
||||||
|
}
|
||||||
|
])"_json;
|
||||||
|
|
||||||
|
static json good_defaulted_person = R"([
|
||||||
|
{
|
||||||
|
"name": "Albert",
|
||||||
|
"age": 42,
|
||||||
|
"address": {"street": "Main Street"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Knut",
|
||||||
|
"age": 69,
|
||||||
|
"address": {}
|
||||||
|
}
|
||||||
|
])"_json;
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -8,11 +8,14 @@
|
|||||||
*/
|
*/
|
||||||
#include <nlohmann/json-schema.hpp>
|
#include <nlohmann/json-schema.hpp>
|
||||||
|
|
||||||
|
#include "json_patch.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
using nlohmann::json_patch;
|
||||||
using nlohmann::json_uri;
|
using nlohmann::json_uri;
|
||||||
using nlohmann::json_schema::root_schema;
|
using nlohmann::json_schema::root_schema;
|
||||||
using namespace nlohmann::json_schema;
|
using namespace nlohmann::json_schema;
|
||||||
@ -43,7 +46,7 @@ public:
|
|||||||
schema(root_schema *root)
|
schema(root_schema *root)
|
||||||
: root_(root) {}
|
: root_(root) {}
|
||||||
|
|
||||||
virtual void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const = 0;
|
virtual void validate(const json::json_pointer &ptr, const json &instance, json_patch &patch, error_handler &e) const = 0;
|
||||||
|
|
||||||
virtual const json &defaultValue(const json::json_pointer &, const json &, error_handler &) const
|
virtual const json &defaultValue(const json::json_pointer &, const json &, error_handler &) const
|
||||||
{
|
{
|
||||||
@ -61,7 +64,7 @@ class schema_ref : public schema
|
|||||||
const std::string id_;
|
const std::string id_;
|
||||||
std::shared_ptr<schema> target_;
|
std::shared_ptr<schema> target_;
|
||||||
|
|
||||||
void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const final
|
void validate(const json::json_pointer &ptr, const json &instance, json_patch &patch, error_handler &e) const final
|
||||||
{
|
{
|
||||||
if (target_)
|
if (target_)
|
||||||
target_->validate(ptr, instance, patch, e);
|
target_->validate(ptr, instance, patch, e);
|
||||||
@ -227,7 +230,7 @@ public:
|
|||||||
} while (1);
|
} while (1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const final
|
void validate(const json::json_pointer &ptr, const json &instance, json_patch &patch, error_handler &e) const final
|
||||||
{
|
{
|
||||||
if (root_)
|
if (root_)
|
||||||
root_->validate(ptr, instance, patch, e);
|
root_->validate(ptr, instance, patch, e);
|
||||||
@ -277,7 +280,7 @@ class logical_not : public schema
|
|||||||
{
|
{
|
||||||
std::shared_ptr<schema> subschema_;
|
std::shared_ptr<schema> subschema_;
|
||||||
|
|
||||||
void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const final
|
void validate(const json::json_pointer &ptr, const json &instance, json_patch &patch, error_handler &e) const final
|
||||||
{
|
{
|
||||||
first_error_handler esub;
|
first_error_handler esub;
|
||||||
subschema_->validate(ptr, instance, patch, esub);
|
subschema_->validate(ptr, instance, patch, esub);
|
||||||
@ -312,7 +315,7 @@ class logical_combination : public schema
|
|||||||
{
|
{
|
||||||
std::vector<std::shared_ptr<schema>> subschemata_;
|
std::vector<std::shared_ptr<schema>> subschemata_;
|
||||||
|
|
||||||
void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const final
|
void validate(const json::json_pointer &ptr, const json &instance, json_patch &patch, error_handler &e) const final
|
||||||
{
|
{
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
|
|
||||||
@ -400,7 +403,7 @@ class type_schema : public schema
|
|||||||
return defaultValue_;
|
return defaultValue_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const override final
|
void validate(const json::json_pointer &ptr, const json &instance, json_patch &patch, error_handler &e) const override final
|
||||||
{
|
{
|
||||||
// depending on the type of instance run the type specific validator - if present
|
// depending on the type of instance run the type specific validator - if present
|
||||||
auto type = type_[(uint8_t) instance.type()];
|
auto type = type_[(uint8_t) instance.type()];
|
||||||
@ -587,7 +590,7 @@ class string : public schema
|
|||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
void validate(const json::json_pointer &ptr, const json &instance, json &, error_handler &e) const override
|
void validate(const json::json_pointer &ptr, const json &instance, json_patch &, error_handler &e) const override
|
||||||
{
|
{
|
||||||
if (minLength_.first) {
|
if (minLength_.first) {
|
||||||
if (utf8_length(instance) < minLength_.second) {
|
if (utf8_length(instance) < minLength_.second) {
|
||||||
@ -677,7 +680,7 @@ class numeric : public schema
|
|||||||
return std::fabs(res) > std::fabs(eps);
|
return std::fabs(res) > std::fabs(eps);
|
||||||
}
|
}
|
||||||
|
|
||||||
void validate(const json::json_pointer &ptr, const json &instance, json &, error_handler &e) const override
|
void validate(const json::json_pointer &ptr, const json &instance, json_patch &, error_handler &e) const override
|
||||||
{
|
{
|
||||||
T value = instance; // conversion of json to value_type
|
T value = instance; // conversion of json to value_type
|
||||||
|
|
||||||
@ -736,7 +739,7 @@ public:
|
|||||||
|
|
||||||
class null : public schema
|
class null : public schema
|
||||||
{
|
{
|
||||||
void validate(const json::json_pointer &ptr, const json &instance, json &, error_handler &e) const override
|
void validate(const json::json_pointer &ptr, const json &instance, json_patch &, error_handler &e) const override
|
||||||
{
|
{
|
||||||
if (!instance.is_null())
|
if (!instance.is_null())
|
||||||
e.error(ptr, instance, "expected to be null");
|
e.error(ptr, instance, "expected to be null");
|
||||||
@ -749,7 +752,7 @@ public:
|
|||||||
|
|
||||||
class boolean_type : public schema
|
class boolean_type : public schema
|
||||||
{
|
{
|
||||||
void validate(const json::json_pointer &, const json &, json &, error_handler &) const override {}
|
void validate(const json::json_pointer &, const json &, json_patch &, error_handler &) const override {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
boolean_type(json &, root_schema *root)
|
boolean_type(json &, root_schema *root)
|
||||||
@ -759,7 +762,7 @@ public:
|
|||||||
class boolean : public schema
|
class boolean : public schema
|
||||||
{
|
{
|
||||||
bool true_;
|
bool true_;
|
||||||
void validate(const json::json_pointer &ptr, const json &instance, json &, error_handler &e) const override
|
void validate(const json::json_pointer &ptr, const json &instance, json_patch &, error_handler &e) const override
|
||||||
{
|
{
|
||||||
if (!true_) { // false schema
|
if (!true_) { // false schema
|
||||||
// empty array
|
// empty array
|
||||||
@ -783,7 +786,7 @@ class required : public schema
|
|||||||
{
|
{
|
||||||
const std::vector<std::string> required_;
|
const std::vector<std::string> required_;
|
||||||
|
|
||||||
void validate(const json::json_pointer &ptr, const json &instance, json &, error_handler &e) const override final
|
void validate(const json::json_pointer &ptr, const json &instance, json_patch &, error_handler &e) const override final
|
||||||
{
|
{
|
||||||
for (auto &r : required_)
|
for (auto &r : required_)
|
||||||
if (instance.find(r) == instance.end())
|
if (instance.find(r) == instance.end())
|
||||||
@ -811,7 +814,7 @@ class object : public schema
|
|||||||
|
|
||||||
std::shared_ptr<schema> propertyNames_;
|
std::shared_ptr<schema> propertyNames_;
|
||||||
|
|
||||||
void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const override
|
void validate(const json::json_pointer &ptr, const json &instance, json_patch &patch, error_handler &e) const override
|
||||||
{
|
{
|
||||||
if (maxProperties_.first && instance.size() > maxProperties_.second)
|
if (maxProperties_.first && instance.size() > maxProperties_.second)
|
||||||
e.error(ptr, instance, "too many properties");
|
e.error(ptr, instance, "too many properties");
|
||||||
@ -860,7 +863,7 @@ class object : public schema
|
|||||||
if (instance.end() == finding) { // if the prop is not in the instance
|
if (instance.end() == finding) { // if the prop is not in the instance
|
||||||
const auto &defaultValue = prop.second->defaultValue(ptr, instance, e);
|
const auto &defaultValue = prop.second->defaultValue(ptr, instance, e);
|
||||||
if (!defaultValue.empty()) { // if default value is available
|
if (!defaultValue.empty()) { // if default value is available
|
||||||
patch[ptr / prop.first] = defaultValue;
|
patch.add((ptr / prop.first), defaultValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -963,7 +966,7 @@ class array : public schema
|
|||||||
|
|
||||||
std::shared_ptr<schema> contains_;
|
std::shared_ptr<schema> contains_;
|
||||||
|
|
||||||
void validate(const json::json_pointer &ptr, const json &instance, json &patch, error_handler &e) const override
|
void validate(const json::json_pointer &ptr, const json &instance, json_patch &patch, error_handler &e) const override
|
||||||
{
|
{
|
||||||
if (maxItems_.first && instance.size() > maxItems_.second)
|
if (maxItems_.first && instance.size() > maxItems_.second)
|
||||||
e.error(ptr, instance, "array has too many items");
|
e.error(ptr, instance, "array has too many items");
|
||||||
@ -1224,9 +1227,9 @@ json json_validator::validate(const json &instance) const
|
|||||||
json json_validator::validate(const json &instance, error_handler &err) const
|
json json_validator::validate(const json &instance, error_handler &err) const
|
||||||
{
|
{
|
||||||
json::json_pointer ptr;
|
json::json_pointer ptr;
|
||||||
json mergePatch{};
|
json_patch patch{};
|
||||||
root_->validate(ptr, instance, mergePatch, err);
|
root_->validate(ptr, instance, patch, err);
|
||||||
return mergePatch;
|
return patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace json_schema
|
} // namespace json_schema
|
||||||
|
|||||||
78
src/json_patch.cpp
Normal file
78
src/json_patch.cpp
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
#include "json_patch.hpp"
|
||||||
|
|
||||||
|
namespace nlohmann
|
||||||
|
{
|
||||||
|
|
||||||
|
json_patch::json_patch()
|
||||||
|
: j_{R"([])"_json} {}
|
||||||
|
|
||||||
|
json_patch::json_patch(json &&patch)
|
||||||
|
: j_{std::move(patch)}
|
||||||
|
{
|
||||||
|
validateJsonPatch(j_);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_patch::json_patch(const json &patch)
|
||||||
|
: j_{std::move(patch)}
|
||||||
|
{
|
||||||
|
validateJsonPatch(j_);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_patch &json_patch::add(std::string path, json value)
|
||||||
|
{
|
||||||
|
j_.push_back(json{{"op", "add"}, {"path", std::move(path)}, {"value", std::move(value)}});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_patch &json_patch::replace(std::string path, json value)
|
||||||
|
{
|
||||||
|
j_.push_back(json{{"op", "replace"}, {"path", std::move(path)}, {"value", std::move(value)}});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_patch &json_patch::remove(std::string path)
|
||||||
|
{
|
||||||
|
j_.push_back(json{{"op", "remove"}, {"path", std::move(path)}});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void json_patch::validateJsonPatch(json const &patch)
|
||||||
|
{
|
||||||
|
if (!patch.is_array()) {
|
||||||
|
throw JsonPatchFormatException{"Json is not an array"};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const &op : patch) {
|
||||||
|
if (!op.is_object()) {
|
||||||
|
throw JsonPatchFormatException{"Each json patch entry needs to be an op object"};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!op.contains("op")) {
|
||||||
|
throw JsonPatchFormatException{"Each json patch entry needs op element"};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto opType = op["op"].get<std::string>();
|
||||||
|
if ((opType != "add") && (opType != "remove") && (opType != "replace")) {
|
||||||
|
throw JsonPatchFormatException{std::string{"Operation "} + opType + std::string{"is invalid"}};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!op.contains("path")) {
|
||||||
|
throw JsonPatchFormatException{"Each json patch entry needs path element"};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// try parse to path
|
||||||
|
[[maybe_unused]] const auto p = json::json_pointer{op["path"].get<std::string>()};
|
||||||
|
} catch (json::exception &e) {
|
||||||
|
throw JsonPatchFormatException{e.what()};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opType != "remove") {
|
||||||
|
if (!op.contains("value")) {
|
||||||
|
throw JsonPatchFormatException{"Remove and replace needs value element"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nlohmann
|
||||||
38
src/json_patch.hpp
Normal file
38
src/json_patch.hpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace nlohmann
|
||||||
|
{
|
||||||
|
class JsonPatchFormatException : public std::exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit JsonPatchFormatException(std::string msg)
|
||||||
|
: ex_{std::move(msg)} {}
|
||||||
|
|
||||||
|
inline const char *what() const noexcept override final { return ex_.c_str(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string ex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class json_patch
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
json_patch();
|
||||||
|
json_patch(json &&patch);
|
||||||
|
json_patch(const json &patch);
|
||||||
|
|
||||||
|
json_patch &add(std::string path, json value);
|
||||||
|
json_patch &replace(std::string path, json value);
|
||||||
|
json_patch &remove(std::string path);
|
||||||
|
|
||||||
|
operator json() const { return j_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
json j_;
|
||||||
|
|
||||||
|
static void validateJsonPatch(json const &patch);
|
||||||
|
};
|
||||||
|
} // namespace nlohmann
|
||||||
Loading…
Reference in New Issue
Block a user