Skip to content

Migrating legacy OpenSemanticWorld (OSW/OSL) schemas to OO-LD

This guide maps the schema dialect used by OpenSemanticWorld page packages and OpenSemanticLab (OSL) instances onto OO-LD. It is backed by a complete scan of every world.opensemantic.* page package (see legacy-keyword-inventory.md, generated by scripts/extract-legacy-keywords.mjs), which lists every legacy keyword, its usage patterns, and its future equivalent.

The big picture

A legacy schema lives in one of two places: the jsonschema slot of a Category-namespace page (*.slot_jsonschema.json), or the main slot of a JsonSchema-namespace page (.../JsonSchema/*.slot_main.json). Both use the same dialect and migrate identically.

Legacy OSW schemas mix four concerns in one document. OO-LD keeps them but gives each a clearly owned namespace:

Concern Legacy form OO-LD target
Semantics (identity, types, relations) uuid, range, subclassof_range, rdf_type, @context x-oold-* + @context
Portable UI intent options.hidden, propertyOrder, enum_titles, infoText, widget format values x-oold-ui-*
Renderer-specific UI json-editor options.*, watch, headerTemplate, flatpickr x-jedison-* (json-editor's successor)
Server-side logic eval_template, dynamic_template, SMW query, data maps x-osl-*

The split lets one schema carry OO-LD semantics and jedison UI hints together, each clearly owned, while generic JSON Schema 2020-12 validators ignore every x- keyword.

Keyword mapping (summary)

The full, per-keyword table with real examples is in legacy-keyword-inventory.md. The frequent cases:

Legacy OO-LD Notes
uuid x-oold-uuid schema identity
range x-oold-range IRI, array of IRIs, or an OO-LD subschema
subclassof_range x-oold-subclassof-range range constraint on subclass targets
rdf_type x-oold-instance-rdf-type rdf:type(s) carried by instances
title* / description* (schema level) x-oold-multilang-title / x-oold-multilang-description multilang language maps; NOT the @context multi-mapping notation
id $anchor OR x-jedison-id (choose) see the note under Pitfalls before reusing $anchor
enumSource $data (value source only) $data is a non-standard proposal; per-item title/value templating stays x-jedison-*
readonly readOnly standard JSON Schema spelling (camelCase)
propertyOrder x-oold-ui-property-order jedison x-categoryOrder
options.category x-oold-ui-property-group jedison x-category / x-propGroup
options.hidden x-oold-ui-form-hidden jedison x-hidden
options.enum_titles (+ enum_titles*) x-oold-ui-enum-titles jedison x-enumTitles; * carries language maps
options.infoText x-oold-ui-hint jedison x-info
defaultProperties (root array) x-oold-ui-default-property (per-property boolean) moves to each subschema so composition can override it
non-standard format values (table, tabs, autocomplete, ...) x-oold-ui-widget registered formats (date, uri, uuid, ...) stay in format
x-enum-varnames x-enum-varnames (kept) established code-gen names; pair with x-enum-descriptions
watch, headerTemplate, labelTemplate, links, flatpickr, json-editor options.* x-jedison-* renderer-specific, no portable meaning
eval_template, dynamic_template, template, SMW query, source_path/target_path/field_maps x-osl-* OSL server-side mustache / SMW
x-smw-quantity-property x-smw-* Semantic MediaWiki binding
role, modes, conditional_visible/conditional_hide deferred query-form constructs, no current OO-LD equivalent

Worked example

A simplified excerpt of the real world.opensemantic.core Entity schema (uuid ce353767-c628-45bd-9d88-d6eb3009aec0). Most properties are omitted for brevity, the @context is trimmed to a few entries, and the label render template is abbreviated; the keyword mapping table above is the complete reference. Full keyword coverage (range, enum/enum_titles, x-enum-varnames, ...) lives in the table, not in this one schema.

Before (legacy OSW)

{
  "@context": {
    "schema": "https://schema.org/",
    "skos": "https://www.w3.org/TR/skos-reference/",
    "Property": { "@id": "wiki:Property-3A", "@prefix": true },
    "description": { "@id": "skos:definition" }
  },
  "type": "object",
  "id": "entity",
  "uuid": "ce353767-c628-45bd-9d88-d6eb3009aec0",
  "title": "Entity",
  "required": ["uuid", "label"],
  "defaultProperties": ["description"],
  "properties": {
    "uuid": {
      "title": "UUID",
      "type": "string",
      "format": "uuid",
      "options": { "hidden": true }
    },
    "label": {
      "type": "array",
      "title": "Label(s)",
      "title*": { "de": "Bezeichnung(en)" },
      "format": "table",
      "eval_template": [{ "type": "mustache-wikitext", "mode": "render", "value": "{{=<% %>=}} ... <%/label%>" }],
      "items": { "$ref": "/wiki/JsonSchema:Label?action=raw" },
      "minItems": 1
    },
    "description": {
      "type": "array",
      "title": "Description",
      "title*": { "de": "Beschreibung" },
      "format": "table",
      "items": { "$ref": "/wiki/JsonSchema:Description?action=raw" }
    },
    "image": {
      "title": "Image",
      "title*": { "de": "Bild" },
      "propertyOrder": 1020,
      "type": "string",
      "format": "url"
    },
    "query_label": {
      "title": "Query label",
      "type": "string",
      "options": { "hidden": true, "conditional_visible": { "modes": ["query"] } }
    }
  }
}

After (OO-LD)

{
  "$schema": "https://oo-ld.github.io/oold-schema/latest/meta/oold-meta-schema.json",
  "@context": {
    "schema": "https://schema.org/",
    "skos": "https://www.w3.org/TR/skos-reference/",
    "Property": { "@id": "wiki:Property-3A", "@prefix": true },
    "description": { "@id": "skos:definition" }
  },
  "$id": "Entity.schema.json",
  "x-jedison-id": "entity",
  "x-oold-uuid": "ce353767-c628-45bd-9d88-d6eb3009aec0",
  "title": "Entity",
  "type": "object",
  "required": ["uuid", "label"],
  "properties": {
    "uuid": {
      "title": "UUID",
      "type": "string",
      "format": "uuid",
      "x-oold-ui-form-hidden": true
    },
    "label": {
      "type": "array",
      "title": "Label(s)",
      "x-oold-multilang-title": { "de": "Bezeichnung(en)" },
      "x-oold-ui-widget": "table",
      "x-osl-eval-template": [{ "type": "mustache-wikitext", "mode": "render", "value": "{{=<% %>=}} ... <%/label%>" }],
      "items": { "$ref": "Label.schema.json" },
      "minItems": 1
    },
    "description": {
      "type": "array",
      "title": "Description",
      "x-oold-multilang-title": { "de": "Beschreibung" },
      "x-oold-ui-widget": "table",
      "x-oold-ui-default-property": true,
      "items": { "$ref": "Description.schema.json" }
    },
    "image": {
      "title": "Image",
      "x-oold-multilang-title": { "de": "Bild" },
      "x-oold-ui-property-order": 1020,
      "type": "string",
      "format": "uri"
    },
    "query_label": {
      "title": "Query label",
      "type": "string",
      "x-oold-ui-form-hidden": true
    }
  }
}

What changed:

  • uuid -> x-oold-uuid; the per-property title* -> x-oold-multilang-title.
  • id (the json-editor watch id) -> vendor x-jedison-id, not $anchor (see Pitfalls).
  • Raw-slot $ref URLs (/wiki/JsonSchema:Label?action=raw) become resolvable schema URLs (Label.schema.json, $id-addressable). Ordinary composition still uses standard $ref.
  • options: { "hidden": true } -> x-oold-ui-form-hidden; propertyOrder -> x-oold-ui-property-order.
  • format: "table" is not a registered JSON Schema format, so it moves to x-oold-ui-widget; format: "url" becomes the registered format uri (a registered format such as uuid stays put).
  • eval_template (OSL server-side mustache) -> x-osl-eval-template.
  • defaultProperties: ["description"] is a root array of the optional properties shown by default. Because composed schemas merge these arrays, it is extend-only: a derived schema can add a default property but cannot switch one off. Moving the flag to a per-property boolean x-oold-ui-default-property: true (here on description) makes it overridable - a derived schema resets it to false under most-derived-wins resolution, without restating the whole list. The reverse-property form x-oold-reverse-default-properties (also a root array, same limitation) migrates the same way.
  • options.conditional_visible / modes on query_label have no OO-LD equivalent yet (they drove OSL query forms); record them and drop them, do not invent keywords (see Pitfalls).

Two ways to deliver UI annotations

UI keywords may live inline (as above) or in a separate overlay that leaves the schema untouched, following the OpenAPI Overlay 1.1.0 model. Use an overlay when the schema is generated or owned elsewhere, or when one schema needs different presentation in different contexts.

{
  "overlay": "1.0.0",
  "info": { "title": "Entity form overlay", "version": "1.0.0" },
  "extends": "./Entity.schema.json",
  "actions": [
    { "target": "$.properties.query_label", "update": { "x-oold-ui-form-hidden": false } },
    { "target": "$.properties.image", "update": { "x-oold-ui-widget": "url" } }
  ]
}

See the UI annotations section of the spec for the normative x-oold-ui-* definitions and the overlay format.

Common pitfalls

  • Typos in the legacy corpus. The inventory flags variants such as titel*, desciption, defaultPropertis, _format, flatpicker, #range, $coment. Normalize them to the correct keyword before mapping; do not carry them forward.
  • format misuse. Values like number, string, array and the empty string were used where a JSON Schema type belongs; drop them. url should become the registered format uri.
  • title* is multilang, not multi-mapping. A <base>* sibling of a schema annotation (title*, description*) is a language map and becomes x-oold-multilang-*. The * / ** notation inside @context is a different thing (JSON-LD alternate @id mapping) and is handled by the existing @context rules.
  • enumSource is not a drop-in for $data. $data (json-schema-spec#51) is a non-standard, draft-future proposal, realized today only by ajv's option and the data-2023 vocabulary. It is valid only in the whole-array form ("enum": {"$data": ...}, not "enum": [{"$data": ...}]), supplies just the value list, and limits static analysis. Treat it as an optional extension; the per-item title/value mustache templating has no portable equivalent and stays a x-jedison-* rendering concern.
  • Reusing $anchor for id has implications. $anchor is a real 2020-12 keyword: unique per schema resource, constrained to the anchor name pattern, and referenceable via $ref: "#name". Mapping json-editor's watch id onto it couples UI to validation and breaks on duplicate or non-conforming ids. Prefer vendor x-jedison-id unless you intend watch targets to be resolvable schema anchors.
  • Deferred keywords. conditional_visible, conditional_hide, role and modes were designed for OSL query forms and have no OO-LD equivalent yet; record them, do not invent keywords for them.
  • Server-side templates are not validation. x-osl-* keywords are executed by OSL, not by JSON Schema validators; they are annotations only.

Checklist

  1. Run / read legacy-keyword-inventory.md for the schema's keyword set and decisions.
  2. Replace raw-slot @context / $ref URLs with resolvable schema URLs.
  3. Rename semantic keywords to x-oold-*.
  4. Move portable UI keywords to x-oold-ui-* (inline or overlay); move non-standard format values to x-oold-ui-widget.
  5. Namespace renderer-specific keywords as x-jedison-* and server-side ones as x-osl-*.
  6. Restructure defaultProperties to per-property x-oold-ui-default-property booleans.
  7. Drop typos, format misuse, and JS callbacks; record deferred keywords.
  8. Validate the result with npm run validate. ```