Overlays
Jedison ships an applyOverlay helper that applies an OpenAPI-Overlay-style document to a JSON Schema and returns a new schema.
An overlay is an ordered list of actions, each targeting nodes with a JSONPath and either merging an update into them or remove-ing them.
This lets you layer presentation directives (x-format, x-hidden, readOnly, …) on top of a schema without editing the source schema — useful when the schema is owned elsewhere (a backend, a shared contract) but the form needs UI tweaks per context.
const schema = {
"type": "object",
"title": "Profile",
"properties": {
"name": { "type": "string", "title": "Name" },
"bio": { "type": "string", "title": "Bio" },
"ssn": { "type": "string", "title": "SSN" }
}
}
const overlay = {
"overlay": "1.0.0",
"info": { "title": "Presentation overlay", "version": "1.0.0" },
"actions": [
{ "target": "$.properties.bio", "update": { "x-format": "textarea" } },
{ "target": "$.properties.ssn", "update": { "x-hidden": true } },
{ "target": "$.properties.name", "update": { "readOnly": true } }
]
}
const merged = Jedison.applyOverlay(schema, overlay)
const jedison = new Jedison.Create({
container: document.querySelector('#jedison-container'),
theme: new Jedison.ThemeBootstrap5(),
schema: merged
})
The inputs are never mutated. A malformed overlay throws; an action that matches zero nodes is a no-op.
Overlay document
An overlay document follows the OpenAPI Overlay shape. Only the actions array is required by applyOverlay; overlay and info are conventional metadata.
target— a JSONPath selecting the nodes to act on (required).update— a value merged into every matched node.remove: true— removes every matched node instead of updating.
Actions run in order, so a later action can override an earlier one (last write wins). copy and extends are accepted but ignored.
Targeting (JSONPath subset)
Targets use a small, dependency-free subset of JSONPath (RFC 9535). Every target must start at the root $.
| Syntax | Selects |
|---|---|
$ |
the root (cannot be removed or replaced with a non-object) |
.name, ['name'], ["name"] |
a child by name (bracket form allows any key) |
[n] |
an array item by index (negative counts from the end) |
*, .*, [*] |
all children of an object or array |
.. |
recursive descent (matches at any depth) |
['a','b'], [0,1] |
a union of names or indices |
Filter expressions ([?…]), array slices ([start:end]) and function extensions (length()) are not supported and throw a clear error rather than failing silently.
Merge semantics
update merges into each matched node according to its type:
- Object node — deep-merged (nested objects recurse, arrays concatenate, primitives replace).
- Array node — concatenated (an array update is appended item by item; a non-array update is pushed).
- Primitive node — replaced in place.
remove: true deletes matched object properties and splices matched array items (indices are removed highest-first so they stay valid).
const schema = {
"type": "object",
"properties": {
"address": { "type": "object", "properties": { "city": { "type": "string" } } },
"tags": { "type": "array", "items": { "type": "string" }, "enum": ["a", "b"] },
"status": "draft"
}
}
const overlay = {
"actions": [
{ "target": "$.properties.address", "update": { "properties": { "country": { "type": "string" } } } },
{ "target": "$.properties.tags.enum", "update": ["c"] },
{ "target": "$.properties.status", "update": "published" }
]
}
const merged = Jedison.applyOverlay(schema, overlay)
// merged.properties.address.properties -> { city: {...}, country: {...} }
// merged.properties.tags.enum -> ["a", "b", "c"]
// merged.properties.status -> "published"