{
  "markdown": "# valid_json\n\nModern JSON Schema validation for Erlang/OTP.\n\n`valid_json` is an Erlang/OTP library that validates JSON instances against\nJSON Schema Draft 6, Draft 7, Draft 2019-09, and Draft 2020-12, including\nreferences across all four dialects. Schemas are compiled once when they are\nregistered and are then validated against in one of the four standard output\nformats. The registry is offline — no network requests are made during\nvalidation.\n\n[![CI](https://github.com/Regikul/valid_json/actions/workflows/ci.yml/badge.svg)](https://github.com/Regikul/valid_json/actions/workflows/ci.yml)\n\n## Why valid_json?\n\n`valid_json` fills the niche of a modern JSON Schema validator for Erlang.\n[jesse](https://github.com/for-GET/jesse), the incumbent Erlang validator, has\nnot followed the specification past draft 06. [jsonschex](https://github.com/xinz/jsonschex)\nimplements Draft 2020-12 in full, but it is an Elixir library: using it from\nErlang brings the Elixir toolchain and a struct-shaped API into your build.\n\nIf you are writing Erlang and need Draft 6 through Draft 2020-12, `valid_json`\nprovides JSON Schema support without requiring an Elixir-based validation\nstack.\n\n| Feature | valid_json | jesse | jsonschex |\n| --- | --- | --- | --- |\n| Erlang-native | yes | yes | no (Elixir) |\n| Draft 2020-12 | yes | no (drafts 03, 04, 06) | yes |\n| Draft 2019-09 | yes | no | no |\n| Draft 7 / Draft 6 | yes / yes | no / yes | no / no |\n| Cross-draft references | yes, all four dialects | no | no |\n| `$dynamicRef` / `$recursiveRef` | yes | no | `$dynamicRef` only |\n| `unevaluatedProperties` / `unevaluatedItems` | yes | no | yes |\n| Standard output formats | `flag`, `basic`, `detailed`, `verbose` | own error tuples | own error structs |\n| Runtime dependencies | none | none | optional `jason`, `decimal`, `idna` |\n| Network at validation time | never | possible for unknown `$ref` | whatever your loader does |\n| Command-line tool | `valid-json check DIR` | yes, escript and Docker | no |\n\nSee the full comparison in [docs/comparison](docs/comparison/index.md) — API,\narchitecture, and keyword-by-keyword coverage, measured against jesse 1.8.2 and\njsonschex 0.9.0.\n\n## Installation\n\nThe package is not published on [Hex](https://hex.pm) yet; add it as a Git\ndependency, pinned to the latest tag:\n\n```erlang\n{deps, [\n    {valid_json, {git, \"https://github.com/Regikul/valid_json.git\", {tag, \"v0.4.0\"}}}\n]}.\n```\n\n## Quick start\n\nStart the application so that the built-in meta-schemas are published, then\nvalidate.\n\n### Validate a schema once\n\n`run_schema/3` compiles the schema in the calling process and validates the\ninstance right away — nothing is kept:\n\n```erlang\n{ok, _} = application:ensure_all_started(valid_json),\n\nSchema = #{<<\"type\">> => <<\"integer\">>, <<\"minimum\">> => 0},\n{ok, #{<<\"valid\">> := true}} =\n    valid_json:run_schema(Schema, 5, [{output, flag}]).\n```\n\n### Check a set of schema documents\n\n`valid_json_schema_set:check/2` verifies a complete set of named schema\ndocuments without starting the `valid_json` application, creating ETS tables,\nor retaining compiled artifacts. Register the whole set in one call so that\nreferences between its documents can be resolved:\n\n```erlang\nEntries = [\n    {<<\"root.json\">>,\n     #{<<\"$schema\">> => <<\"https://json-schema.org/draft/2020-12/schema\">>,\n       <<\"$ref\">> => <<\"definitions.json\">>}},\n    {<<\"definitions.json\">>,\n     #{<<\"$schema\">> => <<\"https://json-schema.org/draft/2020-12/schema\">>,\n       <<\"type\">> => <<\"object\">>}}\n],\n\n{ok, Names} =\n    valid_json_schema_set:check(\n      Entries, [{base_uri, <<\"https://example.com/schemas/\">>}]).\n```\n\nThe call accepts `base_uri`, `default_dialect`, and `schema_validation` options.\nIt returns registration errors separately from schema-validation errors, each\npaired with the corresponding document URI. Reading and decoding files remains\nthe caller's responsibility.\n\n### Compile once, validate many times\n\nRegister the schema under its `$id`, then validate by name. Registration\ncompiles the schema; every validation after that is a lookup:\n\n```erlang\n{ok, _} = application:ensure_all_started(valid_json),\n\nName = <<\"https://example.com/schemas/user\">>,\nSchema = #{\n    <<\"$id\">> => Name,\n    <<\"type\">> => <<\"object\">>,\n    <<\"properties\">> => #{\n        <<\"name\">> => #{<<\"type\">> => <<\"string\">>}\n    },\n    <<\"required\">> => [<<\"name\">>]\n},\n{ok, [CanonicalUri]} = valid_json:add(Schema),\n\n{ok, #{<<\"valid\">> := true}} =\n    valid_json:validate(\n        CanonicalUri,\n        #{<<\"name\">> => <<\"Ada\">>},\n        [{output, flag}]).\n```\n\nRegistered schemas are compiled once and reused. Artifacts are stored in a\nsupervised ETS table, so a schema used more than once belongs in a store; a\nschema that arrives with a single request can use `run_schema/3`, at the price\nof compiling it on every call.\n\n## Command line\n\n`rebar3 escriptize` builds `valid-json`, a self-contained escript: the built-in\nmeta-schemas are embedded at compile time, so the command needs neither the\napplication nor the network.\n\n```shell\nrebar3 escriptize\n_build/default/bin/valid-json check priv/schemas\n```\n\n`check DIRECTORY` reads every `.json` document below the directory and checks it\nagainst its meta-schema. The documents are registered together, so a `$ref`\nbetween them resolves. A schema is named by its own `$id`, and by its path below\nthe directory when it has none. `--default-dialect URI` names the dialect for\ndocuments that declare no `$schema`.\n\nStreams are split by what the command is for. Validation results go to stdout as\none standard output document per line — the specification's `detailed` format,\none line per schema that failed, each naming its subject in an extra `instance`\nmember:\n\n```json\n{\n  \"instance\": \"file:///srv/schemas/user.json\",\n  \"valid\": false,\n  \"keywordLocation\": \"\",\n  \"instanceLocation\": \"\",\n  \"absoluteKeywordLocation\": \"https://json-schema.org/draft/2020-12/schema#\",\n  \"errors\": [\n    {\n      \"valid\": false,\n      \"keywordLocation\": \"/allOf/3/$ref/properties/minimum/type\",\n      \"absoluteKeywordLocation\":\n        \"https://json-schema.org/draft/2020-12/meta/validation#/properties/minimum/type\",\n      \"instanceLocation\": \"/minimum\",\n      \"error\": \"expected number, got string\"\n    }\n  ]\n}\n```\n\nPretty-printed here; the stream puts each document on a single line.\n\nEverything the command could not check — a bad argument, an unreadable\ndirectory, a document that would not register or compile — goes to stderr as\nprose. The exit code follows the same split:\n\n| code | meaning |\n| --- | --- |\n| 0 | every schema passed; nothing is printed |\n| 1 | a schema failed its meta-schema; stdout carries the output documents |\n| 2 | something could not be checked; stderr says what |\n\nAn empty directory is exit code 2, not 0: silence there would be\nindistinguishable from a checked set.\n\n## Features\n\n### Supported dialects\n\n- JSON Schema Draft 2020-12\n- JSON Schema Draft 2019-09\n- JSON Schema Draft 7\n- JSON Schema Draft 6\n- Cross-draft references between all four\n\n### References and schema resources\n\n- `$id`, `$anchor`, `$defs`, `definitions`, `$ref`\n- Draft 6/7 fragment-only `$id` targets and their `$ref`-only sibling semantics\n- `$dynamicRef` / `$dynamicAnchor` (Draft 2020-12)\n- `$recursiveRef` / `$recursiveAnchor` (Draft 2019-09)\n- `$vocabulary`, built-in meta-schemas, and user-provided meta-schemas from the\n  store (vocabularies begin with Draft 2019-09)\n- References are resolved eagerly at compile time, so a reference closure is a\n  finite, comparable value — cyclic schemas are not a problem\n\n### Validation\n\n- All standard assertion keywords: `type`, `enum`, `const`, numeric bounds,\n  `pattern`, length and collection-size keywords, `uniqueItems`, `required`,\n  `dependentRequired`, plus both forms of Draft 6/7 `dependencies`\n- Applicators: `allOf`, `anyOf`, `oneOf`, `not`, `if` / `then` / `else`,\n  `dependentSchemas` (`if` / `then` / `else` begin with Draft 7)\n- Object applicators: `properties`, `patternProperties`,\n  `additionalProperties`, `propertyNames`\n- Array applicators: `prefixItems`, `items`, `contains`, `minContains`,\n  `maxContains`, plus the Draft 6/7/2019-09 array form of `items` and\n  `additionalItems`\n- `unevaluatedProperties` and `unevaluatedItems`\n\n### Output\n\n- The four standard output formats of the specification: `flag`, `basic`,\n  `detailed`, and `verbose`\n\n### Registry and storage\n\n- Schemas are compiled once on registration; artifacts live in a supervised\n  ETS table\n- Transactional reload and removal with dependency checking — a document that\n  others reference cannot be removed\n- A directory loader reads `.json` files recursively at startup\n- `run_schema/3` for schemas that are used once\n\n## Schema references and the registry\n\nThe registry is deliberately offline: documents reachable through `$ref` must\nbe registered before compilation. No network requests are made at runtime, so\nvalidation is deterministic, has no latency from fetching, and exposes no\nfetching surface for untrusted schemas.\n\nA document is addressed by its `$id`; a schema that declares none can be named\nfrom the outside with `add_at/1,2` or by the loader. Sets of documents that\nreference one another are registered in one call, because references are\nresolved eagerly:\n\n```erlang\n{ok, [CanonicalUriA, CanonicalUriB]} = valid_json:add([SchemaA, SchemaB]).\n```\n\nThe full identifier model — `$id`, `base_uri`, relative names, embedded\nresources, and the directory loader — is described in\n[Schema resources and identifiers](docs/schema-resources.md).\n\n## Output formats\n\nValidation returns the standard output document of the specification, so `{ok,\nOutput}` may well describe a failed validation — `valid` is a field inside\n`Output`, not the shape of the return. The address itself may be a relative,\nshort name: on a miss it is resolved against the store's `base_uri` before\nreporting `not_found`.\n\n```erlang\n{ok, #{<<\"valid\">> := false, <<\"errors\">> := [_ | _]}} =\n    valid_json:validate(RelativelUri, #{<<\"name\">> => 42}, [{output, detailed}]).\n```\n\n`{error, Reason}` is reserved for not getting as far as evaluating:\n`not_found`, `unavailable`, or an evaluation error.\n\n## Specification compliance\n\n`valid_json` runs the declared validation profile from the official\n[JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite)\nfor all four dialects. The pinned conformance run executes **1355 test groups\nand 6125 test cases**, plus the **8 official output test cases** (standardized\nonly for Draft 2019-09 and Draft 2020-12) and 58 remote documents used by\n`refRemote` tests. Remote documents are compiled under their own `$schema`, so\nthe run also verifies cross-draft resolution; they are registered in advance,\nand validation makes no network requests.\n\nThe declared capability profiles include:\n\n- `optional/bignum`, `optional/id`, `optional/non-bmp-regex`, and\n  `optional/unknownKeyword` in all four dialects;\n- `optional/anchor` and `optional/no-schema` in Draft 2019-09 and Draft\n  2020-12, `optional/cross-draft` where the suite supplies it, and the Draft\n  2020-12 `optional/dynamicRef` profile;\n- a `format` profile compiled with `{assert_format, true}`: 10 files in Draft\n  6, 14 in Draft 7, and 16 each in Draft 2019-09 and Draft 2020-12. The four\n  IDN/IRI files and the A-label group are declared exclusions where present;\n- the official output tests, which pin the `basic` format; `flag`, `detailed`,\n  and `verbose` are covered by the project's own golden tests, because the\n  official suite does not exercise them.\n\nEvery schema resource is checked against its own meta-schema when it is\ncompiled by default; a schema that fails its meta-schema is rejected at\nregistration. A caller that has already verified its schemas may pass\n`{trust_schema, true}` to `run_schema/3` or configure the same option on a\nstore. This skips only meta-schema evaluation: dialect and vocabulary\nresolution, reference checks, regex compilation, emitter safety checks, and\ninstance validation still run. `schema_validation` selects the diagnostic\nformat when the check is enabled.\n\n`trust_schema` is a store-wide policy. It applies equally to loader startup,\nlater additions, rebuilds, and recovery after an artifact-table restart; use\nseparate stores for trusted and untrusted sources.\nThe conformance policy, including the exact list of files, excluded groups, and\nthe pinned census, lives in [okf/testing/conformance-policy.md](okf/testing/conformance-policy.md).\n\n### Format\n\n`format` is collected as an **annotation** by default in all four dialects.\nFormat assertions are opt-in: compiling with `{assert_format, true}` enables\nstring checking for the implemented formats. An annotation is still collected\nfor a passing value, and a value of a non-string type always passes.\n\n```erlang\nSchema = #{<<\"format\">> => <<\"ipv4\">>},\n\n%% Annotation only: the string passes, whatever it contains.\n{ok, #{<<\"valid\">> := true}} =\n    valid_json:run_schema(Schema, <<\"999.1.1.1\">>, []),\n\n%% With assertions enabled, the value is checked.\n{ok, #{<<\"valid\">> := false}} =\n    valid_json:run_schema(Schema, <<\"999.1.1.1\">>,\n                          [{assert_format, true}]).\n```\n\n| | Formats |\n| --- | --- |\n| Assertion (with `assert_format`) | `date`, `time`, `date-time`, `duration`, `ipv4`, `ipv6`, `hostname`, `email`, `uri`, `uri-reference`, `uri-template`, `json-pointer`, `relative-json-pointer`, `uuid`, `regex` — 15 formats |\n| Annotation only, by declaration | `idn-email`, `idn-hostname`, `iri`, `iri-reference` |\n\nUnknown format names always pass and still produce an annotation. The\nFormat-Assertion vocabulary is not claimed: a meta-schema that declares it\n`true` is rejected, as the specification requires of an implementation that\ndoes not check every format name. `contentEncoding`, `contentMediaType`, and\n`contentSchema` are annotations and do not decode or validate string content.\n\nThe per-format algorithms and their exact boundaries are documented in\n[okf/architecture/format-attributes.md](okf/architecture/format-attributes.md).\n\n## Known limitations\n\n- **Regular expression dialect.** `pattern`, `patternProperties`, and\n  `format: regex` are compiled with Erlang's `re` module, which is PCRE rather\n  than ECMA-262. A pattern outside the subset shared by both dialects — for\n  example `\\p{Letter}` — fails to compile and the whole schema is rejected.\n  The ECMA-262/PCRE differences are measured in\n  [okf/architecture/ecma-to-pcre-adaptation.md](okf/architecture/ecma-to-pcre-adaptation.md).\n- **IDN and IRI formats.** `idn-email`, `idn-hostname`, `iri`, and\n  `iri-reference` are always annotations: the string itself is not checked.\n  This is a declared exclusion of the profile, not a temporary gap.\n- **`hostname` and A-labels.** An `xn--…` label is treated as an ordinary LDH\n  label: A-labels are not decoded and IDNA2008 rules are not applied to their\n  contents.\n- **Numeric precision.** `multipleOf` and numeric comparisons are computed on\n  doubles, so a decimal fraction with no exact binary representation can\n  disagree with decimal arithmetic.\n- **Content keywords.** `contentEncoding`, `contentMediaType`, and\n  `contentSchema` are annotations only; string content is not decoded.\n\n## Documentation\n\n- [Comparison with jesse and jsonschex](docs/comparison/index.md) — the full\n  checklist, split into [API](docs/comparison/api.md),\n  [architecture](docs/comparison/architecture.md), and\n  [keyword coverage](docs/comparison/keywords.md)\n- [Schema resources and identifiers](docs/schema-resources.md) — naming,\n  the registry, the loader, and custom stores\n- [okf/](okf/index.md) — normative documents: architecture, core contract,\n  conformance policy, and format attributes\n- [ROADMAP.md](ROADMAP.md) — the implementation checklist, phase by phase\n\n## Requirements\n\n- Erlang/OTP **20 or later**. CI runs the full suite (compile, conformance,\n  EUnit) on OTP 20 through 29.\n- [rebar3](https://rebar3.org/)\n\nOTP releases before 27 use vendored copies of the stdlib `json` and `uri_string`\nmodules, taken from OTP 28.1.1 and compiled only on the old releases. This is\nwhat keeps `{deps, []}` empty — the library has no third-party dependencies, on\nany OTP version. See [THIRD_PARTY.md](THIRD_PARTY.md).\n\n## Development\n\n```shell\nrebar3 compile\nrebar3 eunit\nrebar3 conformance\n```\n\n`conformance` is an alias that runs the conformance profiles alone — the JSON\nSchema Test Suite and the official output tests — without the remaining unit\ntests. In CI, the `ci` profile turns compiler warnings into errors and runs\n`rebar3 as ci compile`, `rebar3 as ci conformance`, and `rebar3 as ci eunit`.\n\n## Project status\n\n`valid_json` is version 0.4.0 and is under active development. Draft 6, Draft\n7, Draft 2019-09, and Draft 2020-12 are supported within the conformance\nprofile declared above; the remaining work is tracked in\n[ROADMAP.md](ROADMAP.md) — the Format-Assertion vocabulary of phase P8, the\nHTTP loader, and the cross-cutting items.\n\nThe records' `reason` and `location` fields are the stable error contract; the\nwording produced by `format_error/1` is an implementation detail and may change.\nThe public API may have breaking changes before 1.0.\n\n## License\n\nLicensed under the [Apache License 2.0](LICENSE.md).\n",
  "bytes": 17257,
  "sha": "c5883fe469557d6acaa4210f7cd6f89a6e63c446f1a2167fc15535d8b7ee191c",
  "repo_slug": "regikul/valid_json",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_regikul_valid_json_okf_index_md_9930fb16/readme"
}