Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ if(valijson_BUILD_TESTS)
tests/test_jsoncpp_adapter.cpp
tests/test_nlohmann_json_adapter.cpp
tests/test_rapidjson_adapter.cpp
tests/test_schema_parser_dialects.cpp
tests/test_picojson_adapter.cpp
tests/test_poly_constraint.cpp
tests/test_uri.cpp
Expand Down
18 changes: 9 additions & 9 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,26 @@ The road to full draft 2020-12 support.
## Prerequisites

* ~~Update to latest version of JSON-Schema-Test-Suite~~
* Add `kDraft202012` parser mode
* ~~Add `kDraft202012` parser mode~~

## Non-annotation keywords

* `$id` parsing (**in progress**)
* `$defs` alias/support
* ~~`$id` parsing~~
* ~~`$defs` alias/support~~
* `items` alternative behaviour for 2020 (**in progress**)
* `dependentRequired`
* `dependentSchemas`
* `prefixItems`
* `items` alternative behaviour for 2020
* `minContains`/`maxContains`, may be achievable before full annotation support

## General

* common `format` behaviours
* schema registry and canonical URI handling

## References

* `$ref` as applicator with siblings
* schema registry and canonical URI handling
* `$anchor` support
* compound schema documents
* remote reference cache improvements
Expand All @@ -38,7 +42,3 @@ The road to full draft 2020-12 support.
* `$dynamicAnchor`
* `$dynamicRef`
* dynamic-scope evaluation

## General

* `format` behaviours
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,12 +371,20 @@ Documents: 2, Iterations: 1000000 (301487 per second)

## JSON Schema Support

Valijson supports most of the constraints defined in [Draft 7](https://json-schema.org/draft-07/json-schema-release-notes.html)
Valijson supports most of the constraints defined in [Draft 7](https://json-schema.org/draft-07/json-schema-release-notes.html).

The main exceptions are
- default
- format

### Draft 2020-12

An experimental `SchemaParser::kDraft202012` mode is available as a starting point for JSON Schema Draft 2020-12 support. It currently enables the dialect selection path, boolean schemas, `$id` scope handling, and `$defs` aliases for legacy `definitions` references. Basic Draft 2020-12 array item assertions (`prefixItems`, new `items`, and `unevaluatedItems`) have also been implemented.

Full Draft 2020-12 support still requires vocabulary handling, complete annotation propagation, `unevaluatedProperties`, and `$dynamicRef`/`$dynamicAnchor`.

### JSON References

Support for JSON References is in development. It is mostly working, however some of the test cases added to [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite) for v6/v7 are still failing.

## JSON Inspector
Expand Down
35 changes: 34 additions & 1 deletion doc/design/schema_parser.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ This version of `populateSchema()` is typically invoked using default values (`n

The initial call to `populateSchema()` doesn't do much, as it is primarily an entry point for a recursive parsing process. The initial call sets up a document cache and schema registry, which are used to minimise unnecessary work and to help resolve cycles. Then it calls `resolveThenPopulateSchema()`, which is where the real work begins.

## Dialects

`SchemaParser` is constructed with a `Version` value. The default is `kDraft7`, and the parser also supports `kDraft3`, `kDraft4`, and the experimental `kDraft202012` mode.

The dialect changes how some keywords are parsed:

* Draft 3 treats property-level `required` and `extends` as legacy syntax.
* Draft 7 and Draft 2020-12 accept boolean schemas and Draft 7-era keywords such as `contains`, `const`, `if`, `then`, and `else`.
* Draft 2020-12 reads `$id`, treats `$defs` as the replacement for `definitions`, and aliases legacy `#/definitions/...` references to `#/$defs/...` while this mode is active.
* Draft 2020-12 gives array applicators their newer meaning: `prefixItems` is used for tuple validation, `items` applies to elements after the tuple prefix, and `additionalItems` is ignored.

## Resolve Then Populate

This step is a little more complicated. This occurs in `resolveThenPopulateSchema()`, which is declared as:
Expand Down Expand Up @@ -54,6 +65,22 @@ If a JSON Reference is found, we must first resolve it. This may involve using t

Resolving a JSON Reference may require a recursive call to `resolveThenPopulateSchema()`.

## Document Cache

The document cache is a map from resolved document URI to the external document returned by the `fetchDoc` callback. It is only used when remote document fetching is enabled. Fetching must be enabled by providing both `fetchDoc` and `freeDoc`; providing only one of them is rejected before parsing begins.

When the parser resolves a `$ref` to a document URI outside the current scope, it first checks this cache. A cache miss calls `fetchDoc`, stores the returned document pointer, and continues parsing through that document's adapter type. After parsing succeeds or throws, the cache is released with `freeDoc`.

The document cache owns external documents for the lifetime of one top-level `populateSchema()` call. It does not cache local subschemas; local reuse is handled by the schema registry.

## Schema Registry

The schema registry maps canonical lookup keys to populated `Subschema` instances. Keys are built from the current resolution scope and JSON Pointer path, or from resolved `$ref` targets. The registry prevents duplicate parsing and breaks reference cycles by allowing later lookups to reuse an existing `Subschema`.

`makeOrReuseSchema()` records registry keys encountered while chasing `$ref` chains. Once it reaches a concrete schema node, all pending keys are registered against the concrete `Subschema`. If the concrete node was already registered, the existing `Subschema` is reused instead of creating and populating a duplicate.

The registry is intentionally stricter than a plain `std::map` lookup. `querySchemaRegistry()` never creates missing entries, and `updateSchemaRegistry()` throws if a key is registered twice. Duplicate registration indicates parser bookkeeping has gone wrong rather than malformed user input.

## Populate Schema

The next step in parsing a schema is a recursive call to `populateSchema()`. The recursive version of this function is declared as:
Expand All @@ -76,6 +103,8 @@ void populateSchema(

This is a huge function that searches for all of the supported JSON Schema rules (referred to in Valijson as 'constraints'). When a supported rule is found, it is parsed and instantiated as subclass of the `Constraint` class.

In Draft 2020-12 mode, `populateSchema()` also switches the array-keyword path. Instead of treating an array-valued `items` as tuple validation, it builds a `LinearItemsConstraint` from `prefixItems`, uses `items` as the schema for remaining elements, and falls back to `unevaluatedItems` when no `items` schema is present. `additionalItems` is not consulted in this mode.

## Constraints

Constraints are Valijson's internal representation of JSON Schema validation keywords, which can later be applied when validating a document. An example would be the `required` keyword. From JSON Schema Draft 4 onwards the value associated with this keyword is an array of property names that must be present on an object being validated.
Expand Down Expand Up @@ -113,4 +142,8 @@ const Subschema * makeOrReuseSchema(
SchemaRegistry &schemaRegistry)
```

The return value is a `Subschema *`, which may be retrieved from the schema registry (described below).
The return value is a `Subschema *`, which may be retrieved from the schema registry.

For Draft 2020-12 array keywords, `makeDraft202012ItemsConstraint()` reuses `LinearItemsConstraint` with a mode flag that records whether the schema for remaining array elements came from `items` or `unevaluatedItems`. This keeps validation compatible with the existing linear tuple machinery while preserving enough information for error messages and basic evaluated-item tracking.

The current `unevaluatedItems` implementation is intentionally limited: it tracks the highest contiguous array index count evaluated by item applicators. That supports direct `prefixItems` cases and simple annotation flow such as `allOf`, but it does not model the full Draft 2020-12 annotation semantics for all combiners, `$ref`, `$dynamicRef`, or `contains` interactions.
Loading
Loading