File format
A model is one JSON file. Here is every field in it.
Overview
A model is stored as *.orm.json: the conceptual schema and its diagram layout in one
id-addressed structure. The diagram is a VS Code custom editor over that text document, so the
dirty indicator, undo stack, file watching and source control all behave normally — and
ORM: Open Model Source (JSON) shows the text at any time.
Elements refer to each other by id, never by position, so hand edits and merges are safe. Missing
collections are repaired on load, which means the smallest valid model is {}.
For how this format compares with NORMA .orm, the FBM Exchange MetaModel, the Unified
Modelling Schema and Apache Ossie — and why Factum keeps a format of its own — see
File formats.
{
"$schema": "https://www.factum-orm.com/schema/orm-model-2.schema.json",
"version": 2,
"name": "HR",
"objectTypes": [
{ "id": "ot_person", "name": "Person", "kind": "entity",
"refMode": "nr", "dataType": "integer" },
{ "id": "ot_gender", "name": "GenderCode", "kind": "value",
"dataType": "string", "dataTypeLength": 1 }
],
"factTypes": [
{
"id": "ft_works",
"roles": [
{ "id": "r1", "objectTypeId": "ot_person" },
{ "id": "r2", "objectTypeId": "ot_company" }
],
"readings": [
{ "id": "rd1", "roleOrder": ["r1", "r2"],
"text": "{0} works for {1}", "isPrimary": true },
{ "id": "rd2", "roleOrder": ["r2", "r1"], "text": "{0} employs {1}" }
]
}
],
"subtypeRelations": [
{ "id": "st1", "subtypeId": "ot_manager", "supertypeId": "ot_person",
"isPreferredIdentificationPath": true }
],
"constraints": [
{ "id": "uc1", "kind": "uniqueness", "roles": ["r1"] },
{ "id": "mc1", "kind": "mandatory", "roles": ["r1"] },
{ "id": "vc1", "kind": "value", "objectTypeId": "ot_gender",
"ranges": [{ "value": "M" }, { "value": "F" }] }
],
"diagram": {
"shapes": {
"ot_person": { "x": 120, "y": 200 },
"ft_works": { "x": 290, "y": 150, "orientation": "horizontal" }
}
}
}
Object types
| Field | Type | Meaning |
|---|---|---|
id | string | Unique within the model |
name | string | Display name; must be unique |
kind | entity | value | Entity types need a reference scheme; value types are lexical |
refMode | string | Reference mode, e.g. nr in Person(.nr) |
dataType | string | string, text, integer, decimal, float, money, boolean, date, time, dateTime, guid, binary, autoCounter |
dataTypeLength, dataTypeScale | number | Size for sized types |
isIndependent | boolean | May have instances that play no roles — drawn as ! |
isPersonal | boolean | Verbalized with who rather than that |
objectifiedFactTypeId | string | Set when this entity type objectifies a fact type |
note | string | Free text |
Fact types, roles and readings
A fact type holds an ordered list of roles and one or more
readings. A role names the object type that plays it
(objectTypeId, or null while unattached) and may carry a
name.
Readings use {0}, {1}, … placeholders indexing into
roleOrder — the same convention NORMA uses — so the same fact type can be read in
either direction. Mark one reading isPrimary; it is the one shown on the diagram.
A fact type may also be isDerived, with a derivationRule and an optional
isStored flag. Derivation rules are stored and verbalized, not evaluated.
A reading may carry a lang — a BCP 47 tag such as en or nl
— falling back to the model’s own lang. A multilingual model therefore keeps
its languages through a round trip.
Constraints
Every constraint has an id, a kind, an optional name and an
optional modality (alethic, the default, or deontic).
kind | Shape |
|---|---|
uniqueness | roles: [], optional isPreferredIdentifier |
mandatory | roles: [] — one role is simple, several is disjunctive |
frequency | roles: [], min, max (null for unbounded) |
ring | roles: [a, b], types: [] — irreflexive, symmetric, acyclic, … |
subset, exclusion, equality | roleSequences: [[…], […]]; for subset, the first is the subset |
value | objectTypeId or roleId, plus ranges: [] |
cardinality | objectTypeId or roleId, plus min, max |
subtypeSet | supertypeId, subtypeRelationIds: [], isExclusive, isExhaustive |
A value range is either a single value, or a min/max pair
with optional minInclusive and maxInclusive flags.
Diagram
diagram.shapes maps an element id to its position: x, y,
optional w/h, and for fact types an orientation of
horizontal or vertical. External constraints may carry their own
position; anything without one is placed automatically. Deleting a shape entry simply re-places
the element — the model itself is unaffected.
Sample populations
A fact type may carry a population: sample tuples, one value per role in the fact
type’s own role order. A value type may carry a flat list of the values it takes.
{
"id": "ft_works",
"roles": [ … ],
"readings": [ { "text": "{0} works for {1}", … } ],
"population": [
{ "values": ["101", "Acme"] },
{ "values": ["102", "Globex"] }
]
}
Populations are what fact-based modelling starts from — the modeller writes example sentences and the model follows. Keeping them in the file makes four things possible:
- the verbalizer substitutes the real values back into the reading, so a domain expert reads “101 works for Acme” rather than a placeholder;
- the validator checks the constraints you drew against the examples you gave — a uniqueness, mandatory, frequency or value constraint the data contradicts is reported;
- the examples become seed data and test fixtures;
- FBM and NORMA round trips stop discarding the instances they carry.
Metadata
Every element — the model, object types, fact types, roles, readings, subtype relations and
constraints — may carry a meta object. Nothing in it changes what the conceptual
schema means; it is there so a model survives a round trip through another fact-based modeling
tool, and so generators have something to say beyond an element’s name.
| Field | Meaning | Counterpart elsewhere |
|---|---|---|
guid | Stable cross-tool identity | FBM GUID, NORMA element ids |
uri | IRI denoting the element | RDF/OWL, ontology alignment |
title | Display name when it differs from name | — |
shortDescription, description | Documentation | FBM ShortDescription, LongDescription |
synonyms | Alternative names | FBM Synonyms, Ossie ai_context.synonyms |
tags | Free classification, e.g. pii | — |
aiContext | instructions, synonyms, examples | Ossie ai_context |
source | tool, version, ref — set by importers | — |
meta.description is not inert: both mappers prefer it to their generated comment, so
documentation written once in the model reaches the SQL and the Cypher.
Schema generation hints
A hints object attaches per-target guidance for generation. The rule that keeps hints
safe is that a hint never changes the conceptual schema, only how it is rendered
— strip every hint from a file and the model still says exactly the same thing. A name given in a
hint is a physical name and is emitted verbatim, without the case conversion the mappers
otherwise apply.
| Hint | Applies to | Effect |
|---|---|---|
relational.schemaName | model | Qualifies every generated table |
relational.tableName | object type, fact type | Physical table name |
relational.columnName | value type, role | Physical column name |
relational.sqlType | value type | SQL type, used instead of the one derived from dataType |
relational.mapping | fact type | absorb or separateTable |
graph.label | object type, fact type | Node label or relationship type |
graph.labels | object type | Additional node labels |
graph.propertyName | value type, role | Name of an absorbed property |
graph.mapping | value type | node or property |
{
"id": "ot_person", "name": "Person", "kind": "entity", "refMode": "nr",
"meta": {
"description": "A human being known to the business.",
"synonyms": ["Party"]
},
"hints": {
"relational": { "tableName": "HR_PERSON" },
"graph": { "label": "Employee", "labels": ["Party"] }
}
}
A hint that would make the generated schema lose facts is refused rather than obeyed, and the
refusal appears in the mapping notes. Asking for
graph.mapping: "property" on a value type played many-to-many is the case that
arises in practice: a single-valued property cannot hold many values, so it becomes a node
anyway.
Unknown target keys are legal and preserved, so another tool can carry
hints.ossie or hints.typedb without a change to this format.
Extensions
Any key beginning with x- is an extension: legal anywhere, ignored by the editor, and
written back unchanged. This is the same convention OpenAPI uses, and it is how another tool can
round-trip data Factum has no concept for.
The loader is more permissive than the schema on purpose. It preserves every
unrecognised top-level key, not only x- ones, so a misspelled key shows up as a
warning in the editor rather than disappearing on the next save.
Schema and version
The format is described by a JSON Schema (2020-12) published at
schema/orm-model-2.schema.json and shipped with the extension, so VS Code validates
and completes a .orm.json file in the plain text editor. Point $schema
at it and any other tool, in any language, can validate against the same contract.
version is the format’s major version, currently 2. Version 2 is
additive over version 1 — it only adds optional meta, hints and
lang keys — so a version 1 file is a valid version 2 file, is upgraded on load, and
is written back as version 2 on the next save.