2026-09-28 · 8 min read
- json-schema
- validation
- arrays
Modern JSON Schema separates positional tuple validation into prefixItems and validation of remaining locations into items. unevaluatedItems adds a composition-aware boundary: after applicable successful validation has run, it constrains array locations that were not evaluated.
This distinction lets a reusable tuple participate in allOf or conditional designs without placing a premature closed boundary inside the base schema. It also means correctness depends on dialect support and annotation flow across the complete schema.
Model the array vocabulary first
Write down the meaning of each position, whether later items are homogeneous, minimum and maximum length, uniqueness rules, and whether extension through composition is intentional. Use object properties instead when positions are numerous, optional, or hard for users to identify.
- Use prefixItems for ordered positional schemas.
- Use schema-valued items for the remaining sequence.
- Use contains for membership, not tuple position.
- Use unevaluatedItems only when composition requires it.
Pin a dialect and migration strategy
Declare the intended $schema URI and verify every runtime, editor, gateway, and code generator supports that dialect. Older drafts used an array-valued items keyword with additionalItems; mechanically mixing that form with prefixItems changes semantics.
During migration, maintain fixtures for both accepted and rejected arrays and compare validators before changing the published schema. Do not rely on an implementation silently treating an unknown keyword as enforcement.
Understand successful evaluation
An array location can become evaluated through prefixItems, items, contains, or annotations propagated from successful applicators and references according to the chosen dialect. A branch that fails validation does not contribute successful annotations.
With anyOf, several branches may succeed and their annotations can matter. With oneOf, the instance must satisfy exactly one branch. Design branch constraints so selection is explicit rather than relying on incidental item overlap.
Place closure at the right boundary
Apply unevaluatedItems where the complete composed contract is visible. Closing a base tuple with items set to false can prevent an allOf extension from adding positions because that boundary is evaluated before the sibling schema can rescue the extra item.
Keep reusable bases open only when extension is part of their contract. Uncontrolled openness can admit accidental payload fields, while scattered closure rules make compatibility changes difficult to reason about.
Publish an index-level compatibility suite
Use Flashman's JSON formatter for schemas and fixtures, diff for validator results, YAML/JSON converter for configuration forms, regex tester for patterned strings, and Markdown editor for the position contract.
Cover empty and exact-length arrays, one and several extra items, contains matches, failed and multiple successful branches, nested arrays, references, conditional paths, unsupported dialects, draft migration, validator upgrades, and generated clients.