JSON schema validator for JSON for Modern C++
Go to file
Cristian Le 3e44edf330
Add basic cmake documentation structure
Signed-off-by: Cristian Le <git@lecris.dev>
2023-07-09 22:46:18 +02:00
.github/workflows Modernize cmake script (#262) 2023-05-11 12:07:56 +02:00
cmake Modernize cmake script (#262) 2023-05-11 12:07:56 +02:00
docs Add basic cmake documentation structure 2023-07-09 22:46:18 +02:00
example Modernize cmake script (#262) 2023-05-11 12:07:56 +02:00
src Cancel patch change for each non valid logical combination (#231) 2023-07-04 13:03:37 +02:00
test Cancel patch change for each non valid logical combination (#231) 2023-07-04 13:03:37 +02:00
.clang-format Modernize cmake script (#262) 2023-05-11 12:07:56 +02:00
.gitignore More gitignore 2023-07-09 21:22:24 +02:00
.pre-commit-config.yaml Add pre-commit for sphinx/myst 2023-07-09 21:56:16 +02:00
.readthedocs.yaml Add basic sphinx/rtd framework 2023-07-09 21:37:34 +02:00
CMakeLists.txt Revert "Replace the full nlohmann_json repo with a fetch_content variant. (#279)" 2023-07-07 12:40:52 +02:00
CMakePresets.json Reconfigure CI (#259) 2023-05-10 19:49:27 +02:00
conanfile.py Updates conanfile.py 2022-09-03 09:22:23 +02:00
LICENSE license: license-filename changed 2016-12-28 16:08:41 +01:00
pyproject.toml Add basic pyproject.toml for dev and docs 2023-07-09 21:25:15 +02:00
README.md Simplify cmake documentation 2023-07-09 22:40:09 +02:00
schema Complete rewrite of the validator - aiming a 2.0-release 2018-12-27 16:59:19 +01:00

JSON schema validator for JSON for Modern C++

This is a C++ library for validating JSON documents based on a JSON Schema which itself should validate with draft-7 of JSON Schema Validation.

First a disclaimer: It is work in progress and contributions or hints or discussions are welcome.

Niels Lohmann et al develop a great JSON parser for C++ called JSON for Modern C++. This validator is based on this library, hence the name.

Getting started

Currently, this package only offers a C++ library interface, and is only available via cmake's FetchContent and conan. It is highly recommended to use cmake to link to this library

Dependencies:

  • NLohmann's Json library: At least 3.6.0 (See Github actions for officially tested versions)

CMake configuration

Bellow is a minimum cmake configuration file using FetchContent:

cmake_minimum_required(VERSION 3.11)

project(example)

include(FetchContent)

FetchContent_Declare(nlohmann_json_schema_validator
        GIT_REPOSITORY pboettch/json-schema-validator
        # Please use a specific version tag
        GIT_TAG main
        )
FetchContent_MakeAvailable(nlohmann_json_schema_validator)

add_executable(example main.cpp)
target_link_libraries(example PRIVATE nlohmann_json_schema_validator::validator)

For more details about the available cmake options and recommended configurations see docs/cmake

Api example

See also app/json-schema-validate.cpp.

#include <iostream>
#include <iomanip>

#include <nlohmann/json-schema.hpp>

using nlohmann::json;
using nlohmann::json_schema::json_validator;

// The schema is defined based upon a string literal
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
        }
    },
    "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}};

int main()
{
    /* json-parse the schema */

    json_validator validator; // create validator

    try {
        validator.set_root_schema(person_schema); // insert root-schema
    } catch (const std::exception &e) {
        std::cerr << "Validation of schema failed, here is why: " << e.what() << "\n";
        return EXIT_FAILURE;
    }

    /* json-parse the people - API of 1.0.0, default throwing error handler */

    for (auto &person : {bad_person, good_person}) {
        std::cout << "About to validate this person:\n"
                  << std::setw(2) << person << std::endl;
        try {
            validator.validate(person); // validate the document - uses the default throwing error-handler
            std::cout << "Validation succeeded\n";
        } catch (const std::exception &e) {
            std::cerr << "Validation failed, here is why: " << e.what() << "\n";
        }
    }

    /* json-parse the people - with custom error handler */
    class custom_error_handler : public nlohmann::json_schema::basic_error_handler
    {
        void error(const nlohmann::json_pointer<nlohmann::basic_json<>> &pointer, const json &instance,
            const std::string &message) override
        {
            nlohmann::json_schema::basic_error_handler::error(pointer, instance, message);
            std::cerr << "ERROR: '" << pointer << "' - '" << instance << "': " << message << "\n";
        }
    };


    for (auto &person : {bad_person, good_person}) {
        std::cout << "About to validate this person:\n"
                  << std::setw(2) << person << std::endl;

        custom_error_handler err;
        validator.validate(person, err); // validate the document

        if (err)
            std::cerr << "Validation failed\n";
        else
            std::cout << "Validation succeeded\n";
    }

    return EXIT_SUCCESS;
}

Compliance

There is an application which can be used for testing the validator with the JSON-Schema-Test-Suite. In order to simplify the testing, the test-suite is included in the repository.

If you have cloned this repository providing a path the repository-root via the cmake-variable JSON_SCHEMA_TEST_SUITE_PATH will enable the test-target(s).

All required tests are OK.

Format

Optionally JSON-schema-validator can validate predefined or user-defined formats. Therefore a format-checker-function can be provided by the user which is called by the validator when a format-check is required (ie. the schema contains a format-field).

This is how the prototype looks like and how it can be passed to the validation-instance:

static void my_format_checker(const std::string &format, const std::string &value)
{
	if (format == "something") {
		if (!check_value_for_something(value))
			throw std::invalid_argument("value is not a good something");
	} else
		throw std::logic_error("Don't know how to validate " + format);
}

// when creating the validator

json_validator validator(nullptr, // or loader-callback
                         my_format_checker); // create validator

Default Checker

The library contains a default-checker, which does some checks. It needs to be provided manually to the constructor of the validator:

json_validator validator(loader, // or nullptr for no loader
                         nlohmann::json_schema::default_string_format_check);

Supported formats: date-time, date, time, email, hostname, ipv4, ipv6, uuid, regex

More formats can be added in src/string-format-check.cpp. Please contribute implementions for missing json schema draft formats.

Default value processing

As a result of the validation, the library returns a json patch including the default values of the specified schema.

#include <iostream>
#include <nlohmann/json-schema.hpp>

using nlohmann::json;
using nlohmann::json_schema::json_validator;

static const json rectangle_schema = R"(
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "A rectangle",
    "properties": {
        "width": {
            "$ref": "#/definitions/length",
            "default": 20
        },
        "height": {
            "$ref": "#/definitions/length"
        }
    },
    "definitions": {
        "length": {
            "type": "integer",
            "minimum": 1,
            "default": 10
        }
    }
})"_json;

int main()
{
	try {
		json_validator validator{rectangle_schema};
		/* validate empty json -> will be expanded by the default values defined in the schema */
		json rectangle = "{}"_json;
		const auto default_patch = validator.validate(rectangle);
		rectangle = rectangle.patch(default_patch);
		std::cout << rectangle.dump() << std::endl; // {"height":10,"width":20}
	} catch (const std::exception &e) {
		std::cerr << "Validation of schema failed: " << e.what() << "\n";
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}

The example above will output the specified default values {"height":10,"width":20} to stdout.

Note that the default value specified in a $ref may be overridden by the current instance location. Also note that this behavior will break draft-7, but it is compliant to newer drafts (e.g. 2019-09 or 2020-12).