JSON-LD logoProblem

You Already Have the Vocabulary. Why Can't Anything Use It?

Somewhere in most organizations that take meaning seriously, there is one document nobody argues with. It might be a partner's published vocabulary, a domain model built by a standards-literate team, or a profile of schema.org. It is written in JSON-LD: classes and properties in an `@graph`, each carrying a globally unique identifier, with a `@context` declaring exactly which namespace every short name expands into. It is often the most carefully reasoned description of the business anyone in the building owns.

You Already Have the Vocabulary. Why Can't Anything Use It?

Somewhere in most organizations that take meaning seriously, there is one document nobody argues with. It might be a partner's published vocabulary, a domain model built by a standards-literate team, or a profile of schema.org. It is written in JSON-LD: classes and properties in an @graph, each carrying a globally unique identifier, with a @context declaring exactly which namespace every short name expands into. It is often the most carefully reasoned description of the business anyone in the building owns.

And nothing downstream can execute it. Not the validation layer, not the warehouse, not the event stream. The vocabulary is a description; the pipeline needs a schema. So somebody translates — and that translation is where the value quietly leaks out.

The translation tax nobody budgets for

The translation is almost never a script. It is a person with two windows open, retyping one document into the other, making a decision on every line.

Does a property whose range is another class become a foreign key, a nested object, or a denormalized column? Does an enumeration become a string field, a lookup table, or a check constraint? Should a property the vocabulary lists against three classes appear on all three tables, or just the one this sprint needs? Each answer is defensible. None are written down anywhere except in the artifact that results — and the artifact does not explain itself.

Multiply by consumers: the API team needs JSON Schema, the warehouse team needs DDL in its dialect, the streaming team needs Avro. Each translation happens separately, under a different deadline. The organization ends up holding four descriptions of the same world and no mechanism that can prove they agree.

What dies first is identity

The most valuable property of a JSON-LD vocabulary is the one that survives translation least often.

In JSON-LD, a property is not called name. It is https://schema.org/name, compacted through a prefix the document declares. That IRI is the entire point: it is what makes two independently built datasets provably about the same concept rather than coincidentally similar. It is the part of the vocabulary that cannot drift, because it is not a word — it is an address.

Plain JSON Schema has no obvious slot for it. Neither does a CREATE TABLE statement. So in the retyping, the IRI is the first thing dropped, because dropping it costs nothing on the day and everything afterwards. The resulting column is called name, which is a rumor. The next engineer who asks "is this the same name as the one in the partner feed?" gets an opinion instead of an answer.

The open world meets the closed world

Even a careful translator faces a structural mismatch, not a cosmetic one.

RDF vocabularies are open-world: they say what exists, never that the list is complete. A vocabulary says a Person has a name; it does not say a person must have one. Record schemas run the opposite way — required fields are the backbone of every contract. A translator therefore has to either invent obligations the vocabulary never asserted, or discard distinctions it did.

Properties are first-class in RDF, too. schema:domainIncludes is a list: one property can legitimately belong to several classes at once, while in a record schema a field lives in exactly one object. Flattening that is a real modeling decision, made silently, per line.

Enumerations complete the picture. Schema.org-style vocabularies model a controlled list as a class that is a subclass of schema:Enumeration, with every permitted value as its own identified node — each value has an IRI too. A record schema models the same thing as an inline array of strings. Translate by hand and the members' identities evaporate along with the property's.

Then version two of the vocabulary ships. Nobody re-runs the crosswalk, because the crosswalk was never a program. Now there are three descriptions in play: the old vocabulary, the new one, and operational schemas derived from a version nobody can name.

What we built instead

CoreModels treats JSON-LD as a first-class schema format, in both directions. The format key is jsonld: you can import a vocabulary into a governed project, and you can export a governed project as a standard @context-plus-@graph document.

On import, the structural decisions are made once, by a coder, the same way every time. A node typed rdfs:Class becomes a Type. rdfs:subClassOf between classes becomes inheritance, so an Employee declared as a subclass of Person arrives as exactly that. A node typed rdf:Property becomes an Element attached to every Type its schema:domainIncludes names — the many-domains reality is preserved, not flattened. schema:rangeIncludes decides the element's type: XSD and schema.org datatypes land as integers, decimals, booleans and date-times; a range naming another class becomes a genuine reference between Types; a class marked as a subclass of schema:Enumeration becomes a Taxonomy, and its member nodes become terms that keep their original identifiers.

Identity travels. Because every @id in JSON-LD is an IRI, the import lifts each one into a mapsTo annotation on the node it creates — prefix and expanded URI, resolved through the document's own @context. Import schema:name and the element still knows, machine-readably, that it is https://schema.org/name. That knowledge is not stranded in one format either: our tests prove that a vocabulary imported from JSON-LD and encoded as JSON Schema carries the schema.org URI along as an x-maps-to annotation on the property.

And where the two worlds genuinely disagree, we take the honest option. RDF is open-world, so imported properties default to optional — we will not manufacture a constraint the source never asserted. The @context prefix map is preserved, along with the details a faithful re-emit needs: each property's original range identifier and each enumeration member's full IRI. Decode, encode, and decode again is structurally stable, and we hold ourselves to that with a test rather than a promise.

Every transform response also carries a lossiness list — a typed, path-carrying, plain-English record of anything a conversion could not represent exactly. The judgment calls that used to live in one person's memory are now stated by the system that made them, on every call.

The point

The vocabulary was never the problem. The border was. Once the crossing is a governed, repeatable operation instead of an afternoon of retyping, the careful work someone already did stops being a document that only humans can read — and starts being the source every other schema in the estate is derived from.

If this describes a spreadsheet you currently maintain, the transform section of the CoreModels docs walks through the import end to end.