NAME

JSON::Schema::Validate - Lean, recursion-safe JSON Schema validator (Draft 2020-12)

SYNOPSIS

use JSON::Schema::Validate;
use JSON ();

my $schema = {
    '$schema' => 'https://json-schema.org/draft/2020-12/schema',
    '$id'     => 'https://example.org/s/root.json',
    type      => 'object',
    required  => [ 'name' ],
    properties => {
        name => { type => 'string', minLength => 1 },
        next => { '$dynamicRef' => '#Node' },
    },
    '$dynamicAnchor' => 'Node',
    additionalProperties => JSON::false,
};

my $js = JSON::Schema::Validate->new( $schema )
    ->register_builtin_formats;

my $ok = $js->validate({ name => 'head', next=>{ name => 'tail' } })
    or die $js->error;

print "ok\n";

VERSION

v0.1.0

DESCRIPTION

JSON::Schema::Validate is a compact, dependency-light validator for JSON Schema draft 2020-12. It focuses on:

  • Correctness and recursion safety (supports $ref, $dynamicRef, $anchor, $dynamicAnchor).

  • Draft 2020-12 evaluation semantics, including unevaluatedItems and unevaluatedProperties with annotation tracking.

  • A practical Perl API (constructor takes the schema; call validate with your data; inspect error / errors on failure).

  • Builtin validators for common formats (date, time, email, hostname, ip, uri, uuid, JSON Pointer, etc.), with the option to register or override custom format handlers.

This module is intentionally minimal compared to large reference implementations, but it implements the parts most people rely on in production.

Supported Keywords (2020-12)

  • Types

    type (string or array of strings), including union types. Unions may also include inline schemas (e.g. type => [ 'integer', { minimum => 0 } ]).

  • Constant / Enumerations

    const, enum.

  • Numbers

    multipleOf, minimum, maximum, exclusiveMinimum, exclusiveMaximum.

  • Strings

    minLength, maxLength, pattern, format.

  • Arrays

    prefixItems, items, contains, minContains, maxContains, uniqueItems, unevaluatedItems.

  • Objects

    properties, patternProperties, additionalProperties, propertyNames, required, dependentRequired, dependentSchemas, unevaluatedProperties.

  • Combinators

    allOf, anyOf, oneOf, not.

  • Conditionals

    if, then, else.

  • Referencing

    $id, $anchor, $ref, $dynamicAnchor, $dynamicRef.

Formats

Call register_builtin_formats to install default validators for the following format names:

  • date-time, date, time, duration

    Leverages DateTime and DateTime::Format::ISO8601 when available (falls back to strict regex checks). Duration uses DateTime::Duration.

  • email, idn-email

    Uses Regexp::Common with Email::Address if available.

  • hostname, idn-hostname

    idn-hostname uses Net::IDN::Encode if available; otherwise, applies a permissive Unicode label check and then hostname rules.

  • ipv4, ipv6

    Strict regex-based validation.

  • uri, uri-reference, iri

    Reasonable regex checks for scheme and reference forms (not a full RFC parser).

  • uuid

    Hyphenated 8-4-4-4-12 hex.

  • json-pointer, relative-json-pointer

    Conformant to RFC 6901 and the relative variant used by JSON Schema.

  • regex

    Checks that the pattern compiles in Perl.

Custom formats can be registered or override builtins via register_format or the format => { ... } constructor option (see "METHODS").

METHODS

new

my $js = JSON::Schema::Validate->new( $schema, %opts );

Build a validator from a decoded JSON Schema (Perl hash/array structure).

Options (all optional):

format => \%callbacks

Hash of format_name => sub { ... } validators. Each sub receives the string to validate and must return true/false. Entries here take precedence when you later call register_builtin_formats (i.e. your callbacks remain in place).

fnormalize_instance => 1|0

Defaults to 1

When true, the instance is round-tripped through JSON before validation, which enforces strict JSON typing (strings remain strings; numbers remain numbers). This matches Python jsonschema’s type behaviour. Set to 0 if you prefer Perl’s permissive numeric/string duality.

register_builtin_formats

$js->register_builtin_formats;

Registers the built-in validators listed in "Formats". Existing user-supplied format callbacks are preserved if they already exist under the same name.

register_format

$js->register_format( $name, sub { ... } );

Register or override a format validator at runtime. The sub receives a single scalar (the candidate string) and must return true/false.

set_resolver

$js->set_resolver( sub { my( $absolute_uri ) = @_; ...; return $schema_hashref } );

Install a resolver for external documents. It is called with an absolute URI (formed from the current base $id and the $ref) and must return a Perl hash reference representation of a JSON Schema. If the returned hash contains '$id', it will become the new base for that document; otherwise, the absolute URI is used as its base.

validate

my $ok = $js->validate( $data );

Validate a decoded JSON instance against the compiled schema. Returns a boolean. On failure, inspect $js->error for a concise message (first error), or $js->errors for an arrayref of hashes like:

{ path => '#/properties~1name', msg => 'string shorter than minLength 1' }

error

my $msg = $js->error;

Short, human-oriented message for the first failure.

errors

my $arrayref = $js->errors;

All collected errors (up to the internal max_errors cap).

BEHAVIOUR NOTES

  • Recursion & Cycles

    The validator guards on the pair (schema_pointer, instance_address), so self-referential schemas and cyclic instance graphs won’t infinite-loop.

  • Union Types with Inline Schemas

    type may be an array mixing string type names and inline schemas. Any inline schema that validates the instance makes the type check succeed.

  • Booleans

    For practicality in Perl, type => 'boolean' accepts JSON-like booleans (e.g. true/false, 1/0 as strings) as well as Perl boolean objects (if you use a boolean class). If you need stricter behaviour, you can adapt _match_type or introduce a constructor flag and branch there.

  • Unevaluated*

    Both unevaluatedItems and unevaluatedProperties are enforced using annotation produced by earlier keyword evaluations within the same schema object, matching draft 2020-12 semantics.

  • Unsupported/Not Implemented

    This module intentionally omits some rarely used 2020-12 control keywords such as $vocabulary and $comment processing, and media-related keywords like contentEncoding/contentMediaType. These can be added later if required.

CREDITS

Albert from OpenAI for his invaluable help.

AUTHOR

Jacques Deguest <jack@deguest.jp>

SEE ALSO

perl, DateTime, DateTime::Format::ISO8601, DateTime::Duration, Regexp::Common, Net::IDN::Encode, JSON::PP

COPYRIGHT & LICENSE

Copyright(c) 2025 DEGUEST Pte. Ltd.

All rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.