Open & Dynamic Schemas
Strict and extensible schemas — accepting extra fields with the * marker.
A schema is strict by default: a record MUST contain only the declared fields. Adding a * marker makes it extensible, and an extensible schema accepts fields beyond those declared.
A note on the words. This specification says strict and extensible for schemas, and keeps open and closed for the object syntax — an open object is written without braces, a closed one with them. The two axes are unrelated, and one document can hold all four combinations, so a single pair of words for both would make sentences that cannot be read. Readers arriving from JSON Schema should map strict to
additionalProperties: false.
Strict by default
Extra values in a record are rejected unless the schema opts in with *:
~ $schema: { name: string, age: int }
---
~ John, 30 # ✓
~ Alex, 25, extra1 # ✗ unknown-memberAllowing extra fields with *
Place * after the declared fields to accept extras. Positional extras are keyed by index; keyed extras keep their names:
~ $schema: { name: string, age: int, * }
---
~ John, 30 # ✓
~ Alex, 25, Male, cool # ✓ extras at index 2 and 3
~ { Mia, 28, role: dev } # ✓ extra keyed field "role"* bare is grammar; "*" quoted is a name
The * above is bare, and that is what makes it the wildcard. Quoted, it is an ordinary member name — the same rule that governs the ? and * name suffixes: quoting says this name, exactly.
Data is free to use * as a key; JSON-sourced configuration routinely does:
A member named * does not make its schema extensible, and a wildcard is not a member: it never appears among the schema's member names, and a schema may carry both at once.
Typing the extra fields
*: <type> constrains every extra field; *: { <type>, …constraints } adds constraints:
Map-shaped objects
A wildcard may reference a SchemaDef, which makes {*: $ref} the natural schema for dictionary/map-shaped data — data whose keys are values in their own right (IDs, codes, locale tags) rather than field names. The keys stay in the data section; the wildcard types every value:
The wildcard forms, in full:
{ * } (or * listed last)
open — extra members allowed, any type
{ *: type }
every extra member must match type
{ *: { … } }
every extra member must match the inline SchemaDef
{ *: [type] }
every extra member must be an array of type
{ *: $ref }
every extra member must match the referenced SchemaDef
Declared members and the wildcard compose — { id: string, *: number } requires id and lets any other member be a number.
Dynamic types with anyOf
When a single field must accept more than one type, use anyOf (see Any):
When to use curly braces
Only a root object may be written open. The header of a schema, and a record in the data, may both omit their braces — that is the ordinary form. Every child object MUST be braced.
There is no braceless nested object to get wrong, because dropping the braces does not produce a partial object — it produces a different declaration entirely:
The same holds in the data. A child object without braces is not a short object, it is extra members in the parent, and a strict schema rejects them as unknown-member.
See Also
Last updated
Was this helpful?
