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:
Sven Fink 2020-03-03 11:04:23 +01:00
parent 75853c11ee
commit 6f3005dc75
5 changed files with 192 additions and 48 deletions

View File

@ -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

View File

@ -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()
{

View File

@ -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
View 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
View 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