### jsonschema extension

Since 0.174.0, the jsonschema extension implements Drafts 4, 6, 7, 2019-9 and 2020-12 of the [JSON Schema Specification](https://json-schema.org/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)](https://github.com/danielaparker/jsoncons/tree/main).

### Classes

     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) 

### Functions

     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.

### Compliance 

#### Keywords

The jsoncons implementation passes all required tests in the [JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite/) for the keywords below.
You can check the [Bowtie project](https://github.com/bowtie-json-schema/bowtie) to see how jsoncons [compares with other implementations](https://bowtie.report/).

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

#### Format

The implementation understands the following [formats](https://json-schema.org/understanding-json-schema/reference/string.html#format):

| Format        | Draft 4   | Draft 6   | Draft 7   | Draft 2019-09 | Draft 2020-12 |
|:-------------:|:---------:|:---------:|:---------:|:---------:|:---------:|
| date          |           | 🟢 | 🟢 | 🟢 | 🟢 |
| date-time     | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
| email         | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
| hostname      | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
| ipv4          | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
| ipv6          | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
| json-pointer  |           | 🟢 | 🟢 | 🟢 | 🟢 |
| regex         | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
| time          |           | 🟢 | 🟢 | 🟢 | 🟢 |
| uri (since 1.0.0)| 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | 
| uri-reference (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` 

### Schema dialect

jsoncons supports Drafts 4, 6, 7, 2019-9 and 2020-12 of the [JSON Schema Specification](https://json-schema.org/specification).
It needs to know the dialect of a schema resource in order to know how to process it. If a schema contains a **$schema** dialect identifier,
it uses that. Otherwise, it falls back on a default version you can provide through [evaluation_options](./evaluation_options.md), e.g.

```cpp
auto options = jsonschema::evaluation_options{}
    .default_version(jsonschema::schema_version::draft7());
auto compiled = jsonschema::make_json_schema(schema, options);
```

If no default is provided, the latest supported version is assumed.

### URI resolution

jsoncons requires a user supplied schema resolver  to resolve a URI reference to a schema resoure.
A schema resolver  is a function object with the signature of `resolver` being equivalent to 

    Json fun(const jsoncons::uri& uri);

If unable to resolve the resource, it should return Json::null().

Here is an example of a schema resolver  that resolves a URI to a physical pathname, 

```cpp
std::string root_dir = "./input/jsonschema";
auto resolver = [&](const jsoncons::uri& uri) -> json
    {
        std::string pathname = root_dir + uri.path();

        std::fstream is(pathname.c_str());
        if (!is)
        {
            return json::null();
        }

        return json::parse(is);
    };
```
  
### Default values
  
The JSON Schema Specification includes the ["default" keyword](https://json-schema.org/understanding-json-schema/reference/generic.html)
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.

### Custom error messages (since 1.2.0)

jsoncons supports custom error messages via an opt-in `errorMessage` keyword.
To enable this feature, refer to [custom error messages](./custom-error-messages.md).
  
### Examples

The example schemas are from [JSON Schema Miscellaneous Examples](https://json-schema.org/learn/miscellaneous-examples.html),
the [JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite), and user contributions.

[Three ways of validating](#eg1)  
[Format validation](#eg2)  
[Resolving references to schemas defined in external files](#eg3)  
[Validate before decoding JSON into C++ class objects](#eg4)  
[Default values](#eg5)  

#### Three ways of validating

This example illustrates the use of three overloads of the `validate` function that throw,
invoke a callback function, and write to a `json_visitor`.

```cpp
#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."
    }
]
```


#### Format validation

Since Draft 2019-09, format validation is disabled by default, but may be enabled by setting the 
evaluation option `require_format_validation` to `true`. 

```cpp
#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:
```json
[
    {
        "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."
    }
]
```


#### Resolving references to schemas defined in external files  

In this example, the main schema is defined by

```cpp
    std::string main_schema = R"(
{
    "$id" : "https://www.example.com/main",
    "$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"}
    }
}
    )";
```

The main schema includes a reference using the `$ref` keyword to a
second schema defined in an external file, `name-defs.json`,  

```json
{
    "$id" : "https://www.example.com/other",
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$defs": {
        "orNull": {
            "anyOf": [
                {
                    "type": "null"
                },
                {
                    "$ref": "#"
                }
            ]
        }
    },
    "type": "string"
}
```

When building the main schema, the schema builder needs to resolve the URI 'https://www.example.com/name-defs.json#/$defs/orNull'
via a URI resolver. The resolver does not need to supply that specific subschema, it is enough to supply the schema document '/name-defs.json'. 
The schema builder than processes that schema document and makes multiple entries into an internal validator registry, 
including an entry for 'https://www.example.com/name-defs.json#/$defs/orNull'.

```cpp
int main()
{ 
    json schema = json::parse(main_schema);    

    // 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);

        std::string root_dir = "./input/jsonschema";
        auto resolver = [&](const jsoncons::uri& uri) -> json
            {
                std::cout << "Requested URI: " << uri.string() << "\n";
                std::cout << "base: " << uri.base().string() << ", path: " << uri.path() << "\n\n";

                std::string pathname = root_dir + uri.path();
                std::fstream is(pathname.c_str());
                if (!is)
                {
                    return json::null();
                }

                return json::parse(is);
            };

        // Will call the message handler function object for each schema violation
        compiled.validate(data, reporter);
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << '\n';
    }
}
```
Output:
```
Requested URI: https://www.example.com/name-defs.json#/$defs/orNull
base: https://www.example.com/name-defs.json, path: /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
```


#### Validate before decoding JSON into C++ class objects 

This example illustrates decoding data that validates against "oneOf"
into a `std::variant`.

```cpp
// 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"
    }
}
```


#### Default values

```cpp
#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); 

        // 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"}
```

