Skip to content

JSON Schema mapping

SchemaEditor and ObjectContentEditor share one JsonSchema/JsonSchemaProperty contract (@cratis/components/SchemaEditor also exports the types), and it is a pragmatic authoring subset of JSON Schema, not a general-purpose validator. The contract declares title, name, $id, $ref, type, format, description, properties, items, required and definitions.

The table layout edits the schema document in place and keeps every keyword it does not show. The tree layout (layout='tree') edits a narrower model, the property tree: it converts the schema into properties, edits them, and writes a new schema from them.

Property typeJSON Schema written
Text{ "type": "string" }
Number{ "type": "number" }
Yes or no{ "type": "boolean" }
Date{ "type": "string", "format": "date" }
Time{ "type": "string", "format": "time" }
List of text{ "type": "array", "items": { "type": "string" } }
List of numbers{ "type": "array", "items": { "type": "number" } }
Object{ "type": "object", "properties": { … } }
List of objects{ "type": "array", "items": { "type": "object", "properties": { … } } }

Reading is more forgiving: integer reads as a number, and an unknown or missing type reads as text. A list whose items are not numbers or objects reads as a list of text. The table above is what the editor writes for a property you create or retype; a property you leave alone is written back as it was read.

The tree layout reads and writes these keywords. The table layout offers controls only for required and x-key, when allowRequired or allowKeyProperty enables the matching column; it preserves x-concept and x-generated without editing them.

KeywordWhereMeaning
requiredThe containing object, or the items of a list of objectsNames of the properties that must be present. Omitted when empty
x-conceptThe propertyThe concept the property is typed as. The property still carries the primitive’s type
x-keyThe propertytrue on the property that identifies its object. At most one per object
x-generatedThe propertytrue when the value is created when the command runs rather than entered. The tree’s opt-in Generated toggle sets or clears it

The keywords are exported as CONCEPT_KEYWORD, KEY_KEYWORD and GENERATED_KEYWORD. The Generated toggle is tree-only; the table preserves the keyword without offering a column.

{
"type": "object",
"properties": {
"orderId": { "type": "string", "x-concept": "OrderId", "x-key": true },
"total": { "type": "number" }
},
"required": ["total"]
}

Unknown x- vendor keywords round-trip verbatim on every property node, including nested object properties, array properties and array items nodes. Strings, numbers, booleans, nulls, objects and arrays keep their values. Property.vendorKeywords carries property annotations, and Property.itemVendorKeywords carries item annotations; empty maps are omitted. Rename, required and key edits keep this metadata. A type change keeps property annotations but clears item annotations when the item kind changes, including a change to a non-array type.

Standard keywords the editor does not model round-trip as well, on a property and on the items node of a list: annotations (title, description, $comment, deprecated, readOnly, writeOnly), value keywords (default, enum, const, examples), string, number, list and object constraints (minLength, pattern, minimum, minItems, additionalProperties…), and anything else such as $ref, oneOf or if. Property.schemaKeywords and Property.itemSchemaKeywords carry them like the vendor keyword maps do, and are omitted when empty. A host that owns the tree in controlled mode keeps them on the properties it stores and passes them back unchanged.

The original type and format detail described next, and the original order of keywords and of required names, is not part of the Property shape. It lives in hidden metadata on the property objects the editor hands out. A controlled host must keep those objects by reference or update them with the exported edit helpers such as renameProperty; copying them with a spread, structuredClone or a JSON round trip loses the metadata, and the property is then written from its type alone.

The editor models the property type as text, number, yes or no, date, time, object or list, so it keeps where a property came from while its type is unchanged: integer, a type array such as ["string", "null"], a node without a type, and a format other than date and time are written back exactly as read. Choosing a different type discards that detail along with the keywords that no longer apply. The table layout leaves the whole node in place and drops the keywords that no longer apply when you choose a different type, by the rules in the next section. The two layouts differ in how they decide the type: the table reads the JSON type, the tree reads the property type. A nullable type array such as ["number", "null"] reads as text in the tree, so retyping it drops the number constraints; the table finds no single JSON type in the array and drops them too. In the table, changing between integer and number keeps the number constraints (minimum, maximum, exclusiveMinimum, exclusiveMaximum) and drops multipleOf and the value keywords, while the tree treats both as one number type and keeps everything.

x-generated: true reads as Property.isGenerated. Any other x-generated value stays verbatim in vendorKeywords until an explicit Generated edit replaces or clears it. x-concept and x-key retain their dedicated mapping.

The tree editor also preserves every root key except type, properties and required, which it rebuilds. This keeps root vendor keywords, title, definitions and other root metadata. When using conversion helpers directly, pass the incoming schema as the second argument to propertiesToJsonSchema(properties, schema) for the same behavior. Unedited round trips preserve the order of supported root, property and item keywords as well as their values.

A type change keeps only what still applies to the new type. Choosing the type a property already has, for example typing a number as a concept, keeps everything.

KeywordsAfter a type change
Annotations: title, description, $comment, deprecated, readOnly, writeOnlyKept
Vendor x- keywords, required and x-keyKept as described above; required follows the children
Text constraints: minLength, maxLength, pattern, contentEncoding, contentMediaTypeKept when the new type is text, date or time
Number constraints: minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOfKept when the new type is a number
List constraints: minItems, maxItems, uniqueItems, contains, minContains, maxContainsKept when the new type is a list of any kind; keywords on the items are dropped
Object constraints: minProperties, maxProperties, additionalProperties, patternProperties, propertyNames, dependentRequired, dependentSchemas, unevaluatedPropertiesKept when the new type is an object
Value keywords: default, enum, const, examplesDropped
Type refinements: integer, type arrays, and a format other than the date and time the editor writesDropped
Anything else, such as $ref, allOf, oneOf, anyOf, not, if, then, elseDropped

Names in required keep their original order, and names you add follow them. The tree layout is not a lossless editor for arbitrary JSON Schema. It drops the following:

  • names in required that have no declared property;
  • the keywords listed above when you change a property’s type, and properties and items that no longer fit the type.

Treat the output as authoritative only for the shape the editor supports, and validate it with the application’s own validator.

The conversion is pure, so a host can use it without rendering the editor:

import {
jsonSchemaToProperties,
propertiesToJsonSchema,
setKeyProperty,
setRequiredProperty,
} from '@cratis/components/SchemaEditor';
const properties = jsonSchemaToProperties(schema);
const next = setRequiredProperty(properties, properties[0].id, true);
const written = propertiesToJsonSchema(setKeyProperty(next, next[0].id), schema);
FunctionPurpose
jsonSchemaToProperties, propertiesToJsonSchemaConvert in each direction. Ids are generated on every read
addProperty, addChildProperty, removeProperty, renameProperty, changePropertyTypeImmutable edits of the tree
setRequiredProperty, toggleRequiredProperty, setKeyProperty, setGeneratedPropertyRequiredness, the key and generated values
findPropertyById, findPropertyByName, findSiblingProperties, totalPropertyCountTree queries
conceptPropertyName, uniquePropertyName, findPropertyNameProblemNaming

Property ids belong to one editing session. Persist names, and use findPropertyByName to rebind state kept by name to a freshly converted tree.