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-uri.cpp
|
||||
src/json-validator.cpp
|
||||
src/json_patch.cpp
|
||||
src/string-format-check.cpp)
|
||||
|
||||
target_include_directories(nlohmann_json_schema_validator
|
||||
|
||||
@ -10,41 +10,65 @@ using nlohmann::json_schema::json_validator;
|
||||
static json person_schema = R"(
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "A person",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name",
|
||||
"type": "string"
|
||||
},
|
||||
"age": {
|
||||
"description": "Age of the person",
|
||||
"type": "number",
|
||||
"minimum": 2,
|
||||
"maximum": 200
|
||||
},
|
||||
"address":{
|
||||
"type": "object",
|
||||
"properties":{
|
||||
"street":{
|
||||
"type": "string",
|
||||
"default": "Abbey Road"
|
||||
"title": "Dict",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"title": "A person",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name",
|
||||
"type": "string"
|
||||
},
|
||||
"age": {
|
||||
"description": "Age of the person",
|
||||
"type": "number",
|
||||
"minimum": 2,
|
||||
"maximum": 200
|
||||
},
|
||||
"address":{
|
||||
"type": "object",
|
||||
"properties":{
|
||||
"street":{
|
||||
"type": "string",
|
||||
"default": "Abbey Road"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"age"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"age"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
)"_json;
|
||||
|
||||
// The people are defined with brace initialization
|
||||
static json bad_person = {{"age", 42}};
|
||||
static json good_person = {{"name", "Albert"}, {"age", 42}, {"address", {{"street", "Main Street"}}}};
|
||||
static json good_defaulted_person = {{"name", "Knut"}, {"age", 69}, {"address", {}}};
|
||||
static json bad_person = R"([
|
||||
{"age": 42}
|
||||
])"_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()
|
||||
{
|
||||
|
||||
@ -8,11 +8,14 @@
|
||||
*/
|
||||
#include <nlohmann/json-schema.hpp>
|
||||
|
||||
#include "json_patch.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
using nlohmann::json;
|
||||
using nlohmann::json_patch;
|
||||
using nlohmann::json_uri;
|
||||
using nlohmann::json_schema::root_schema;
|
||||
using namespace nlohmann::json_schema;
|
||||
@ -43,7 +46,7 @@ public:
|
||||
schema(root_schema *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
|
||||
{
|
||||
@ -61,7 +64,7 @@ class schema_ref : public schema
|
||||
const std::string id_;
|
||||
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_)
|
||||
target_->validate(ptr, instance, patch, e);
|
||||
@ -227,7 +230,7 @@ public:
|
||||
} 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_)
|
||||
root_->validate(ptr, instance, patch, e);
|
||||
@ -277,7 +280,7 @@ class logical_not : public schema
|
||||
{
|
||||
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;
|
||||
subschema_->validate(ptr, instance, patch, esub);
|
||||
@ -312,7 +315,7 @@ class logical_combination : public schema
|
||||
{
|
||||
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;
|
||||
|
||||
@ -400,7 +403,7 @@ class type_schema : public schema
|
||||
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
|
||||
auto type = type_[(uint8_t) instance.type()];
|
||||
@ -587,7 +590,7 @@ class string : public schema
|
||||
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 (utf8_length(instance) < minLength_.second) {
|
||||
@ -677,7 +680,7 @@ class numeric : public schema
|
||||
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
|
||||
|
||||
@ -736,7 +739,7 @@ public:
|
||||
|
||||
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())
|
||||
e.error(ptr, instance, "expected to be null");
|
||||
@ -749,7 +752,7 @@ public:
|
||||
|
||||
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:
|
||||
boolean_type(json &, root_schema *root)
|
||||
@ -759,7 +762,7 @@ public:
|
||||
class boolean : public schema
|
||||
{
|
||||
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
|
||||
// empty array
|
||||
@ -783,7 +786,7 @@ class required : public schema
|
||||
{
|
||||
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_)
|
||||
if (instance.find(r) == instance.end())
|
||||
@ -811,7 +814,7 @@ class object : public schema
|
||||
|
||||
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)
|
||||
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
|
||||
const auto &defaultValue = prop.second->defaultValue(ptr, instance, e);
|
||||
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_;
|
||||
|
||||
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)
|
||||
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_pointer ptr;
|
||||
json mergePatch{};
|
||||
root_->validate(ptr, instance, mergePatch, err);
|
||||
return mergePatch;
|
||||
json_patch patch{};
|
||||
root_->validate(ptr, instance, patch, err);
|
||||
return patch;
|
||||
}
|
||||
|
||||
} // 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