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"