Since 0.174.0, the jsonschema extension implements Drafts 4, 6, 7, 2019-9 and 2020-12 of the JSON Schema Specification. Previous versions supported Draft 7.
The documentation below describes the new features for the jsonschema extension since 0.174.0. For earlier releases, please refer to jsonschema (until 0.174.0).
json_schema | A json_schema represents the compiled form of a JSON Schema document. |
evaluation_options | Allows configuration of JSON Schema evaluation. |
validation_message | A message type for reporting errors generated by a keyword. |
schema_version | Supported JSON Schema dialects. |
json_validator | JSON Schema validator. Deprecated (since 0.174.0) |
make_json_schema | Processes a JSON Schema document and returns the compiled form as a json_schema (since 0.174.0).
|
make_schema | Loads a JSON Schema and returns a shared pointer to a json_schema . Deprecated since 0.174.0. Removed in 1.0.0.
|
The jsoncons implementation passes all required tests in the JSON Schema Test Suite for the keywords below.
Keyword | Draft 4 | Draft 6 | Draft 7 | Draft 2019-09 | Draft 2020-12 |
---|---|---|---|---|---|
$anchor | 🟢 | 🟢 | |||
$defs | 🟢 | 🟢 | |||
$dynamicAnchor | 🟢 | ||||
$dynamicRef | 🟢 | ||||
$id | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
$recursiveAnchor | 🟢 | ||||
$recursiveRef | 🟢 | ||||
$ref | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
$vocabulary | 🟢 | 🟢 | |||
additionalItems | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
additionalProperties | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
allOf | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
anyOf | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
const | 🟢 | 🟢 | 🟢 | 🟢 | |
contains | 🟢 | 🟢 | 🟢 | 🟢 | |
contentEncoding | 🟢 | 🟢 | 🟢 | ||
contentMediaType | 🟢 | 🟢 | 🟢 | ||
definitions | 🟢 | 🟢 | 🟢 | ||
dependencies | 🟢 | 🟢 | 🟢 | ||
dependentRequired | 🟢 | 🟢 | |||
dependentSchemas | 🟢 | 🟢 | |||
enum | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
exclusiveMaximum (boolean) | 🟢 | ||||
exclusiveMaximum | 🟢 | 🟢 | 🟢 | 🟢 | |
exclusiveMinimum (boolean) | 🟢 | ||||
exclusiveMinimum | 🟢 | 🟢 | 🟢 | 🟢 | |
if-then-else | 🟢 | 🟢 | 🟢 | ||
items | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
maxContains | 🟢 | 🟢 | |||
minContains | 🟢 | 🟢 | |||
maximum | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
maxItems | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
maxLength | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
maxProperties | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
minimum | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
minItems | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
minLength | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
minProperties | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
multipleOf | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
not | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
oneOf | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
pattern | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
patternProperties | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
prefixItems | 🟢 | ||||
properties | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
propertyNames | 🟢 | 🟢 | 🟢 | 🟢 | |
required | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
type | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
unevaluatedItems | 🟢 | 🟢 | |||
unevaluatedProperties | 🟢 | 🟢 | |||
uniqueItems | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
The implementation understands the following formats:
Format | Draft 4 | Draft 6 | Draft 7 | Draft 2019-09 | Draft 2020-12 |
---|---|---|---|---|---|
date | 🟢 | 🟢 | 🟢 | 🟢 | |
date-time | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
🟢 | 🟢 | 🟢 | 🟢 | 🟢 | |
hostname | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
ipv4 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
ipv6 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
json-pointer | 🟢 | 🟢 | 🟢 | 🟢 | |
regex | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
time | 🟢 | 🟢 | 🟢 | 🟢 | |
uri (since 1.0.0) | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
Any other format type is ignored.
Since Draft 2019-09, format is no longer an assertion by default. It can be configured to be an assertion
by setting the evaluation option require_format_validation
to true
The JSON Schema Specification includes the "default" keyword for specifying a default value, but doesn't prescribe how implementations should use it during validation. Some implementations ignore the default keyword, others support updating the input JSON to fill in a default value for a missing key/value pair. This implementation outputs a JSONPatch document that may be further applied to the input JSON to add the missing key/value pairs.
The example schemas are from JSON Schema Miscellaneous Examples, the JSON Schema Test Suite, and user contributions.
Three ways of validating
Format validation
Using a ResolveURI to resolve references to schemas defined in external files
Validate before decoding JSON into C++ class objects
Default values
This example illustrates the use of three overloads of the validate
function that throw,
invoke a callback function, and write to a json_visitor
.
#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonschema/jsonschema.hpp>
#include <iostream>
using jsoncons::ojson;
namespace jsonschema = jsoncons::jsonschema;
int main()
std::string schema_str = R"(
{
"$id": "https://example.com/arrays.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "A representation of a person, company, organization, or place",
"type": "object",
"properties": {
"fruits": {
"type": "array",
"items": {
"type": "string"
}
},
"vegetables": {
"type": "array",
"items": { "$ref": "#/$defs/veggie" }
}
},
"$defs": {
"veggie": {
"type": "object",
"required": [ "veggieName", "veggieLike" ],
"properties": {
"veggieName": {
"type": "string",
"description": "The name of the vegetable."
},
"veggieLike": {
"type": "boolean",
"description": "Do I like this vegetable?"
}
}
}
}
}
)";
std::string data_str = R"(
{
"fruits": [ "apple", "orange", "pear" ],
"vegetables": [
{
"veggieName": "potato",
"veggieLike": true
},
{
"veggieName": "broccoli",
"veggieLike": "false"
},
{
"veggieName": "carrot",
"veggieLike": false
},
{
"veggieName": "Swiss Chard"
}
]
}
)";
ojson schema = ojson::parse(schema_str);
jsonschema::json_schema<ojson> compiled = jsonschema::make_json_schema(std::move(schema));
ojson data = ojson::parse(data_str);
std::cout << "\n(1) Validate using exceptions\n";
try
{
compiled.validate(data);
}
catch (const std::exception& e)
{
std::cout << e.what() << "\n";
}
std::cout << "\n(2) Validate using reporter callback\n";
auto reporter = [](const jsonschema::validation_message& message) -> jsonschema::walk_result
{
std::cout << message.instance_location().string() << ": " << message.message() << "\n";
return jsonschema::walk_result::advance;
};
compiled.validate(data, reporter);
std::cout << "\n(3) Validate outputting to a json decoder\n";
jsoncons::json_decoder<ojson> decoder;
compiled.validate(data, decoder);
ojson output = decoder.get_result();
std::cout << pretty_print(output) << "\n";
}
Output:
(1) Validate using exceptions
/vegetables/1/veggieLike: Expected boolean, found string
(2) Validate using reporter callback
/vegetables/1/veggieLike: Expected boolean, found string
/vegetables/3: Required property 'veggieLike' not found.
(3) Validate outputting to a json decoder
[
{
"valid": false,
"evaluationPath": "/properties/vegetables/items/$ref/properties/veggieLike/type",
"schemaLocation": "https://example.com/arrays.schema.json#/$defs/veggie/properties/veggieLike",
"instanceLocation": "/vegetables/1/veggieLike",
"error": "Expected boolean, found string"
},
{
"valid": false,
"evaluationPath": "/properties/vegetables/items/$ref/required",
"schemaLocation": "https://example.com/arrays.schema.json#/$defs/veggie/required",
"instanceLocation": "/vegetables/3",
"error": "Required property 'veggieLike' not found."
}
]
Since Draft 2019-09, format validation is disabled by default, but may be enabled by setting the
evaluation option require_format_validation
to true
.
#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonschema/jsonschema.hpp>
#include <iostream>
using jsoncons::json;
using jsoncons::ojson;
namespace jsonschema = jsoncons::jsonschema;
int main()
{
json schema = json::parse(R"(
{
"$id": "/test_schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"Date": {
"format": "date-time",
"type": "string"
}
},
"required": [
"Date"
],
"type": "object",
"unevaluatedProperties": false
}
)");
auto compiled = jsoncons::jsonschema::make_json_schema(schema,
jsonschema::evaluation_options{}.require_format_validation(true));
json data = json::parse(R"(
{ "Date" : "2024-03-19T26:34:56Z" }
)");
jsoncons::json_decoder<ojson> decoder;
compiled.validate(data, decoder);
ojson output = decoder.get_result();
std::cout << pretty_print(output) << "\n";
}
Output:
[
{
"valid": false,
"evaluationPath": "/properties/Date/format",
"schemaLocation": "/test_schema#/properties/Date/format",
"instanceLocation": "/Date",
"error": "'2024-03-19T26:34:56Z' is not a RFC 3339 date-time string."
},
{
"valid": false,
"evaluationPath": "/unevaluatedProperties/Date",
"schemaLocation": "/test_schema",
"instanceLocation": "/Date",
"error": "Unevaluated property 'Date' but the schema does not allow unevaluated properties."
}
]
In this example, the main schema defines a reference using the $ref
property to a
second schema, defined in an external file name-defs.json
,
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"orNull": {
"anyOf": [
{
"type": "null"
},
{
"$ref": "#"
}
]
}
},
"type": "string"
}
jsoncons needs to know how to turn a URI reference to name-defs.json
into a JSON Schema document,
and for that it needs you to provide a ResolveURI
.
#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonschema/jsonschema.hpp>
#include <fstream>
// for brevity
using jsoncons::json;
namespace jsonschema = jsoncons::jsonschema;
// Until 0.174.0, throw a `schema_error` instead of returning json::null()
json resolver(const jsoncons::uri& uri)
{
std::cout << "base: " << uri.base().string() << ", path: " << uri.path() << "\n\n";
std::string pathname = "./input/jsonschema/";
pathname += std::string(uri.path());
std::fstream is(pathname.c_str());
if (!is)
{
return json::null();
}
return json::parse(is);
}
int main()
{
json schema = json::parse(R"(
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "http://localhost:1234/draft2020-12/object",
"type": "object",
"properties": {
"name": {"$ref": "name-defs.json#/$defs/orNull"}
}
}
)");
// Data
json data = json::parse(R"(
{
"name": {
"name": null
}
}
)");
try
{
// Throws schema_error if JSON Schema compilation fails
jsonschema::json_schema<json> compiled = jsonschema::make_json_schema(schema, resolver);
auto reporter = [](const jsonschema::validation_message& msg) -> jsonschema::walk_result
{
std::cout << msg.instance_location().string() << ": " << msg.message() << "\n";
for (const auto& detail : msg.details())
{
std::cout << " " << detail.message() << "\n";
}
return jsonschema::walk_result::advance;
};
// Will call reporter for each schema violation
compiled.validate(data, reporter);
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
}
}
Output:
base: http://localhost:1234/draft2020-12/name-defs.json, path: /draft2020-12/name-defs.json
/name: Must be valid against at least one schema, but found no matching schemas
Expected null, found object
Expected string, found object
This example illustrates decoding data that validates against "oneOf"
into a std::variant
.
// for brevity
using jsoncons::json;
namespace jsonschema = jsoncons::jsonschema;
int main()
{
std::string schema_str = R"(
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "job",
"description": "job properties json schema",
"$defs": {
"os_properties": {
"type": "object",
"properties": {
"command": {
"description": "this is the OS command to run",
"type": "string",
"minLength": 1
}
},
"required": [ "command" ],
"additionalProperties": false
},
"db_properties": {
"type": "object",
"properties": {
"query": {
"description": "this is db query to run",
"type": "string",
"minLength": 1
}
},
"required": [ "query" ],
"additionalProperties": false
},
"api_properties": {
"type": "object",
"properties": {
"target": {
"description": "this is api target to run",
"type": "string",
"minLength": 1
}
},
"required": [ "target" ],
"additionalProperties": false
}
},
"type": "object",
"properties": {
"name": {
"description": "name of the flow",
"type": "string",
"minLength": 1
},
"run": {
"description": "job run properties",
"type": "object",
"oneOf": [
{ "$ref": "#/$defs/os_properties" },
{ "$ref": "#/$defs/db_properties" },
{ "$ref": "#/$defs/api_properties" }
]
}
},
"required": [ "name", "run" ],
"additionalProperties": false
}
)";
std::string data_str = R"(
{
"name": "testing flow",
"run" : {
"command": "some command"
}
}
)";
try
{
json schema = json::parse(schema_str);
json data = json::parse(data_str);
// Throws schema_error if JSON Schema compilation fails
jsonschema::json_schema<json> compiled = jsonschema::make_json_schema(schema);
// Test that input is valid before attempting to decode
if (compiled.is_valid(data))
{
const ns::job_properties v = data.as<ns::job_properties>(); // You don't need to reparse data_str
std::string output;
jsoncons::encode_json_pretty(v, output);
std::cout << output << std::endl;
// Verify that output is valid
json test = json::parse(output);
assert(compiled.is_valid(test));
}
else
{
std::cout << "Invalid input\n";
}
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
}
}
Output:
{
"name": "testing flow",
"run": {
"command": "some command"
}
}
#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonschema/jsonschema.hpp>
#include <jsoncons_ext/jsonpatch/jsonpatch.hpp>
#include <fstream>
// for brevity
using jsoncons::json;
namespace jsonschema = jsoncons::jsonschema;
namespace jsonpatch = jsoncons::jsonpatch;
int main()
{
json schema = json::parse(R"(
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"bar": {
"type": "string",
"minLength": 4,
"default": "bad"
}
}
}
)");
try
{
// Data
json data = json::parse("{}");
// will throw schema_error if JSON Schema compilation fails
jsonschema::json_schema<json> compiled = jsonschema::make_json_schema(schema, resolver);
// will throw a validation_error when a schema violation happens
json patch;
compiled.validate(data, patch);
std::cout << "Patch: " << patch << "\n";
std::cout << "Original data: " << data << "\n";
jsonpatch::apply_patch(data, patch);
std::cout << "Patched data: " << data << "\n\n";
}
catch (const std::exception& e)
{
std::cout << e.what() << "\n";
}
}
Output:
Patch: [{"op":"add","path":"/bar","value":"bad"}]
Original data: {}
Patched data: {"bar":"bad"}