Skip to content

JSON-Schema Validation

crate.validation.jsonSchema validates a vibe Json value against a JSON-Schema document. It covers the subset Crate’s own generated schemas use, has no dependencies beyond vibe.data.json, and is a pure function — no I/O, no allocation of a validator object.

import crate.validation.jsonSchema;
auto errors = validate(schema, data);
if (errors.length > 0) {
foreach (error; errors) {
logError("%s: %s", error.path, error.message);
}
}

An empty result means the value is valid.

struct SchemaError {
string path;
string message;
}

path is a dot-separated JSON path to the offending value, built as the validator descends: address.city, tags.0.name. It is empty for an error on the root value. Array indices appear as plain numeric segments.

KeywordApplies toBehavior
typeanystring, number, integer, boolean, object, array, null. An unrecognized type name always passes
propertiesobjectsValidates each named property that is present; absence is required’s business
requiredobjectsReports one error per missing property, with the property name as the path
itemsarraysApplies one schema to every element
enumanyThe value must equal one of the listed values
oneOfanyThe value must match exactly one branch — zero matches and two matches are both errors
$refanyResolves #/$defs/<name> against the root schema’s own $defs

number accepts both integers and floats; integer accepts only integers.

This is deliberately a subset, and unknown keywords are ignored rather than rejected. Not implemented: allOf, anyOf, not, patternProperties, additionalProperties, minimum/maximum, minLength/maxLength, pattern, format, uniqueItems, minItems/maxItems, and $ref to anything outside the schema’s own $defs. A schema using them validates as if those keywords were absent — it will not report an error for them, and it will not fail loudly either.

Two resolution rules fail open rather than erroring:

  • A $ref that does not start with #/$defs/, or names a definition that is not in $defs, is skipped.
  • An unrecognized type name matches anything.

A null value satisfies any schema. Legacy data uses an explicit null to mean “this optional field has no value”, and rejecting it would fail records that are intentionally empty. The key is still present, so required is satisfied — required asks whether the property exists, not whether it holds a value.

If you need “present and not null”, check for it separately after validation.