TL;DR: The same JSON Schema that validates your data can now declare what that data means. We built a ten-keyword extension vocabulary (
x-jsonld-*) that promotes instances to JSON-LD (the JSON serialisation of RDF). Annotate the schemas you already have, and every payload they validate becomes knowledge-graph-ready. Over the past decade, everyone from MuleSoft to Adobe to the Italian government attempted some version of this bridge, and every attempt fell short of the same hard part: composition. We solved it, and it ships today in the JSON Schema CLI and in the Sourcemeta One registry.
There is a fair chance JSON-LD lives in your head as “the schema.org blob you paste into a web page so Google shows rich results”. But JSON-LD is much more than that. It is one of the credential formats the EU Digital Identity Wallet accepts, which all 27 member states must ship by December 2026. It is the foundation of Microsoft’s Digital Twins Definition Language. It is how hundreds of thousands of machine-learning datasets describe themselves on Hugging Face and Kaggle, how all of EU law is published, and how websites talk to AI agents. JSON-LD is what JSON looks like when it graduates into a knowledge graph.
And yet, from where the JSON Schema world sits, JSON-LD might as well be on another planet. It does not matter which door brought you to JSON Schema: OpenAPI specifications (76% of the modern ones are majority JSON Schema by volume), AsyncAPI message payloads, MCP tool definitions, W3C Web of Things Thing Descriptions, the IETF’s Semantic Definition Format for the Internet of Things, or plain standalone data models of the kind governments now publish in national registries. Sooner or later the same need appears: joining JSON Schema’s validation strengths with JSON-LD’s semantic strengths.
The rest of this article covers how the two languages each won their own layer of the data world, who tried to bridge them over the past decade, where every attempt fell short, and how we made it work.
Our JSON-LD extension vocabulary
We built the bridge as a JSON Schema extension
vocabulary: ten annotation keywords under the x-jsonld- prefix, deliberately
continuing the convention introduced by the Italian government’s REST API
Linked Data Keywords IETF
draft.
Because the keywords are plain x- extensions rather than a formal JSON
Schema
vocabulary,
annotated schemas remain valid for every implementation, including the many
with no vocabulary support at all.
Here is a schema that describes a
schema.org/Order whose seller and customer
reuse a single party definition through
$ref, where a party
reuses a shared address, and where a party is a
schema.org/Person or a
schema.org/Organization depending on its
kind.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"x-jsonld-type": "https://schema.org/Order",
"properties": {
"orderNumber": {
"type": "string",
"x-jsonld-id": "https://schema.org/orderNumber"
},
"seller": {
"$ref": "#/$defs/party",
"x-jsonld-id": "https://schema.org/seller"
},
"customer": {
"$ref": "#/$defs/party",
"x-jsonld-id": "https://schema.org/customer"
}
},
"required": [ "orderNumber", "seller", "customer" ],
"$defs": {
"party": {
"type": "object",
"properties": {
"kind": { "enum": [ "person", "organization" ] },
"name": { "type": "string", "x-jsonld-id": "https://schema.org/name" },
"address": {
"$ref": "#/$defs/address",
"x-jsonld-id": "https://schema.org/address"
}
},
"required": [ "kind", "name" ],
"if": { "properties": { "kind": { "const": "person" } } },
"then": { "x-jsonld-type": "https://schema.org/Person" },
"else": { "x-jsonld-type": "https://schema.org/Organization" }
},
"address": {
"type": "object",
"x-jsonld-type": "https://schema.org/PostalAddress",
"properties": {
"country": {
"type": "string",
"x-jsonld-id": "https://schema.org/addressCountry",
"x-jsonld-self": "http://publications.europa.eu/resource/authority/country/{this}"
}
}
}
}
}
And here is an order payload, one company selling to one individual, the kind that would flow through a commerce API:
{
"orderNumber": "AC-2026-0042",
"seller": {
"kind": "organization",
"name": "ACME GmbH",
"address": { "country": "DEU" }
},
"customer": {
"kind": "person",
"name": "Erika Mustermann",
"address": { "country": "ITA" }
}
}
Now let’s run this through the Sourcemeta JSON Schema CLI:
$ jsonschema rdf schema.json instance.json
Both parties come out of the same
$ref with different
RDF types, the seller an
Organization and the customer a
Person, each carrying an address typed as a
schema.org/PostalAddress with its country
promoted to the EU country
authority IRI:
[
{
"@type": [ "https://schema.org/Order" ],
"https://schema.org/orderNumber": [ { "@value": "AC-2026-0042" } ],
"https://schema.org/seller": [
{
"@type": [ "https://schema.org/Organization" ],
"https://schema.org/name": [ { "@value": "ACME GmbH" } ],
"https://schema.org/address": [
{
"@type": [ "https://schema.org/PostalAddress" ],
"https://schema.org/addressCountry": [
{ "@id": "http://publications.europa.eu/resource/authority/country/DEU" }
]
}
]
}
],
"https://schema.org/customer": [
{
"@type": [ "https://schema.org/Person" ],
"https://schema.org/name": [ { "@value": "Erika Mustermann" } ],
"https://schema.org/address": [
{
"@type": [ "https://schema.org/PostalAddress" ],
"https://schema.org/addressCountry": [
{ "@id": "http://publications.europa.eu/resource/authority/country/ITA" }
]
}
]
}
]
}
]
The seller and customer were typed differently from the same definition, and
the address mapping, written once, applied to both, because the annotations
compose through $ref
exactly as validation does. One schema. One pass. If the instance is invalid,
no RDF comes out at all. That output is in standard
JSON-LD expanded
form, canonical and
context-free.
If you would rather keep the document looking like the JSON your
consumers already parse, compact it against a context of your choosing. For
example, consider this context.json:
{
"@context": {
"schema": "https://schema.org/",
"orderNumber": "schema:orderNumber",
"seller": "schema:seller",
"customer": "schema:customer",
"name": "schema:name",
"address": "schema:address",
"country": {
"@id": "schema:addressCountry",
"@type": "@id",
"@context": { "@base": "http://publications.europa.eu/resource/authority/country/" }
}
}
}
You can convert to JSON-LD emitting the shape your consumers already know how to parse, with its semantics embedded, as follows:
$ jsonschema rdf schema.json instance.json --compact context.json
{
"@type": "schema:Order",
"orderNumber": "AC-2026-0042",
"seller": {
"@type": "schema:Organization",
"name": "ACME GmbH",
"address": { "@type": "schema:PostalAddress", "country": "DEU" }
},
"customer": {
"@type": "schema:Person",
"name": "Erika Mustermann",
"address": { "@type": "schema:PostalAddress", "country": "ITA" }
},
"@context": {
"schema": "https://schema.org/",
"orderNumber": "schema:orderNumber",
"seller": "schema:seller",
"customer": "schema:customer",
"name": "schema:name",
"address": "schema:address",
"country": {
"@id": "schema:addressCountry",
"@type": "@id",
"@context": { "@base": "http://publications.europa.eu/resource/authority/country/" }
}
}
}
In other words, the context is your choice of projection. The schema annotations remain the single source of truth.
JSON-LD and the knowledge graph economy
Look again at what came out of that command. The expanded form is JSON-LD, which is the JSON serialisation of RDF, the W3C’s model for describing anything as a graph of subject-predicate-object (triple) statements. Build enough of those statements and you have a knowledge graph. The idea is simple: give every thing (such as a person, a country, a product, etc) one shared identifier, and record every fact as a link between those identifiers. Do that, and data scattered across systems, formats, and organisations becomes a single connected web that anyone, and any machine (including AI), can query across sources and reason over as a whole. That is the door our example quietly opened, and it leads somewhere very large.
Almost everything you would call authoritative knowledge is already living in this way. Wikidata holds 1.65 billion statements. UniProt, the reference database of protein knowledge, exposes over 190 billion triples through what is widely cited as the largest public SPARQL endpoint on earth, and the US National Institutes of Health publishes its PubChem chemistry database as more than 130 billion triples of its own. Both are nodes in a life-sciences web that interlinks through the Linked Open Data cloud and its 1,658 connected datasets. Governments are wired the same way: every EU legal act is RDF behind a public endpoint, and France’s statistics office, its national library, and Europeana’s 50 million cultural objects all publish in it. Healthcare does the same through HL7 FHIR, which defines an RDF form for the records it moves between systems. So does big tech: Google, Microsoft, Facebook, eBay, and IBM each run their own. And it is how AI now gets its facts, since a model on its own is a parametric memory that hallucinates and the fix is to ground it in a graph, the pattern behind GraphRAG.
None of them do this for tidiness. They do it because the graph is where data turns into value. We know that data is a valuable asset, but raw data is worth only what you can refine it into, and the knowledge graph is the refinery. When Google launched its Knowledge Graph in 2012 under the slogan “things, not strings”, search stopped matching text and started understanding entities. By 2016, Google reported that it held 70 billion facts and answered roughly a third of its 100 billion monthly searches straight from it. The mechanism is RDF’s shared identifiers. They allow independently produced data to become joinable, so the graph is worth more than the databases poured into it, and every entity added compounds the value of the ones already there. And this compounding now has a price. A data.world benchmark measured a language model at 16% accuracy over a raw SQL database and 54% over the same data as a knowledge graph. Gartner finds the organisations with the most mature data foundations achieve up to 65% greater business outcomes. And the knowledge graph market is projected to grow from $1.06 billion in 2024 to $6.93 billion by 2030.
Bridging JSON Schema and JSON-LD
Zooming out, structure and meaning are things you constantly need at once, and
today they live in different languages. JSON Schema
owns structure, whether the container is an OpenAPI
specification,
an AI tool
definition, or a
national data
registry. Then,
JSON-LD owns meaning, as the last section showed. The
catch is that a single JSON document routinely needs both at once, and inside
that document the two languages cannot see each other. A JSON
Schema cannot say that family_name means
schema.org/familyName, and a
JSON-LD context cannot say that family_name is a
required string conditional on the presence of first_name.
properties, type), sprinkled with JSON-LD to add meaning (@context, and @type: saref:LightSwitch pointing at a semantic vocabulary). The two languages sit in one document but do not integrate: the schema keywords cannot see the semantic ones, or the other way round. Source: W3C WoT Thing Description 1.1, Example 2.Now picture closing that gap. For example, the EU wallet schema already validates every credential a wallet API handles. Put the meaning inside it, and that one file also emits the knowledge graph. No second artefact to author, review, version, or keep in sync. The meaning is defined exactly once next to its validation constraints, and every payload pours into the graph for free. In other words, intermixing JSON Schema and JSON-LD gives the RDF world a powerful validation story, and it gives the JSON Schema world a significant upgrade in semantics and knowledge graph integration.
The reason to state the prize so plainly is that the alternative, two artefacts kept in sync by hand, is everywhere, and it is a heavy tax. The Common Workflow Language project named it in 2015, when it abandoned existing schema languages because “none understand linked data”:
“one must maintain separate JSON schema, JSON-LD context, RDF schema, and human documentation, despite significant overlap of content and obvious need for these documents to stay synchronized.”
A decade later that tax is institutional. W3C Verifiable Credentials
2.0, the standard beneath the EU
wallet, carries a JSON-LD @context for meaning and a
separate credentialSchema for
structure in every single credential. GS1’s
EPCIS, the global supply-chain standard, ships
EPCIS-JSON-Schema.json and epcis-context.jsonld side by side in one
repository. FIWARE’s Smart Data
Models keep a
schema for every entry in the catalogue, paired with a JSON-LD context.
Gaia-X,
Europe’s flagship cloud-federation project, pairs a JSON-LD
self-description with a separate SHACL shape for every participant.
Adobe
specified its Experience Data Model in both grammars at once, then had to bolt
on a “Compatibility
Mode”
that flattens the JSON-LD namespacing out for its own platform.
And the request to fix this at the source has sat open on the JSON Schema
issue tracker since April
2017.
Everyone pays the tax. Nobody has figured out how to fully remove it.
The double edge of expressiveness
So now you might wonder, with the prize this clear and the demand this old, why has this stayed unsolved?
Because the obvious fix, embed the @context in the schema, only works on the
easy case. That is what the Italian government’s REST API Linked Data
Keywords IETF
draft does.
This is often enough for standalone simple schemas. But in practice, schemas
are very often not standalone, and very often not simple. At scale, models of
rich cross-referenced schemas are extremely common for schema reusability and
data governance purposes.
What makes it hard to solve this for complex schemas is the very thing JSON
Schema is best at. JSON Schema is a remarkably expressive
language. The meaning of a
property can be split across files with
$ref, combined through
logical applicators like
anyOf, or made
to depend on the data in complex ways through
if/then/else,
and other similar keywords. With
$dynamicRef a
schema even resolves a reference differently depending on the context it is
evaluated in, so there is no static document to analyse ahead of time and the
meaning is decided during evaluation, against the data. This is why validating
Modern JSON Schema is PSPACE-complete. In
comparison, a JSON-LD context has none of this. It is a
flat table of names, and it cannot follow a schema whose properties change
meaning by position or by data. It could never, for instance, produce the order
from our example, where the very same party definition became a
Person or an
Organization depending on a property in
the payload.
If you take this as a criticism, that JSON Schema is simply too complicated, you have it backwards. The complexity is not over-engineering. It is the language earning its keep. Most people who resent JSON Schema for its constraint-driven complexity have simply not yet met the problems that demand such complexity. And when they do, they are thankful that the expressiveness exists.
A decade of attempts
But all of that expressiveness is what a bridge to JSON-LD has to carry across, and it is why no one had built one that holds. Everyone who tried faced the same fork: strip the expressiveness away so the mapping to RDF turns easy, or keep it and try to pay the price. A decade of attempts went both ways, and came up short in both cases.
The strippers made the problem vanish by not having it. The Common Workflow Language’s Schema Salad rebuilt the schema language on top of Avro, while LinkML and the automotive industry’s Catena-X generate JSON Schema from a separate RDF model you must author first. A simpler language projects into RDF more cleanly, but simplicity is the price: you surrender the conditionals and constraints that are the reason to reach for JSON Schema at all, and that no other mainstream schema language matches. “Those languages define structure”, as Charlie Holland puts it, while “JSON Schema defines structure and constraints”.
The embracers kept JSON Schema and hit the wall head-on. The Open Geospatial Consortium’s tooling assembles the context from the schema at build time, and its own source carries a thirty-line comment about “binding bubbling”, where a referenced schema’s mappings silently overwrite the parent’s. Others went sideways: MuleSoft validated existing JSON-LD with JSON Schema before being archived at “pre-alpha”, and RML and the AI-assisted MetaConfigurator add a third artefact to drift from the other two. Even Adobe and Autodesk took runs at this JSON Schema and JSON-LD interoperability problem.
$ref, allOf, anyOf, and oneOf, collecting mappings into one flat @context. In their own words, “the assembly is really schema traversal”. It is re-creating evaluation at build time, and that is why it cannot follow a schema whose meaning depends on the data: with no instance to run against, a static walk has no way to know which if/then/else branch or $dynamicRef target applies. Source: OGC Building Blocks, semantic uplift documentation.Five ways to fall short
For all their differences, the attempts share the same gaps. None couples validation and emission in one pass. None reproduces the composition inside the schema itself, and the closest do it as a lossy build step. None handles conditional meaning, and none mints identifiers from the data.
The most serious attempt is telling in where it came from. Italy runs both worlds at national scale: a network of RDF ontologies that models its public administration as a knowledge graph, and a national platform of services described in OpenAPI and JSON Schema. It felt the collision before anyone else and set out to close it.
Even so, Roberto Polli’s REST API Linked Data Keywords IETF proposal for this problem (now at its ninth revision) admits the limit:
“Automatic composability is not an explicit goal of this specification because of its complexity. […] These issues can be tackled in future versions of this specifications.”
Against what a working solution actually needs, the pattern is stark:
| Attempt | Standard JSON Schema | Single evaluation pass | Composition | Conditional meaning | IRI minting |
|---|---|---|---|---|---|
| Polli draft (embed context) | yes | no | no | no | no |
| OGC (generate context) | yes | no | partial | no | no |
| Schema Salad (new language) | no | no | no | no | partial |
| LinkML / Catena-X, automotive (separate model) | no | no | no | no | no |
| MuleSoft, Salesforce (validate JSON-LD) | partial | no | $ref only |
no | no |
| RML / MetaConfigurator (mapping) | no | no | no | no | yes |
| Adobe XDM (maintain both by hand) | partial | no | no | no | no |
| Sourcemeta | yes | yes | yes | yes | yes |
After a decade of attempts, from academic prototypes to Adobe, Salesforce, and the automotive industry, the wall was still standing. That is where we come in.
How we cracked it
So how do you carry all of that expressiveness into RDF? Every prior attempt treated the schema as a document to be parsed and mapped. We treat it as a program to be run. Sourcemeta maintains Blaze, a high-performance C++ JSON Schema validator that compiles a schema into a low-level program and executes it against an instance. Around that execution we built a full custom JSON-LD engine that rides along, producing the RDF in lockstep with the validation itself. So the meaning is built during evaluation, as part of the process, and the dynamism that defeated every static approach is carried by the run instead of fought.
The result is full support for every construct JSON Schema has, in one file and one pass, with no second artefact to drift. The order from our initial example, which no prior tool could produce due to its use of some advanced keywords, is not a special case. It is simply what the engine does. If a schema can validate your data, it can now describe what that data means.
Use it today
The vocabulary is ten keywords under the x-jsonld- prefix, and the CLI
documentation
is for now the full reference (we are aiming for a proper spec around it soon).
You can use it today from the command line by pointing jsonschema rdf at
a schema and an instance, and get JSON-LD back, exactly
as we did above.
x-jsonld-* keywords of the vocabulary, from the JSON Schema CLI documentation. Each annotates a subschema to map it into JSON-LD: x-jsonld-id for the predicate IRI a property maps to, x-jsonld-type for the node @type, x-jsonld-self to mint a node @id from instance values, and so on. Because they are plain x- extension keywords, annotated schemas stay valid for every other JSON Schema tool. Source: JSON Schema CLI RDF documentationBut the command line is the small version of the idea. The same engine ships in Sourcemeta One, our flagship schema registry, where it runs over HTTP. Every schema the registry governs can turn an instance into JSON-LD on request, making meaning an API call (available through MCP too).
With this, you can send your data to the registry and know what that data means, resolved against the schemas your organisation has already agreed on, so the transformation happens once, at the infrastructure layer, instead of being rebuilt inside every service.
The idea is for schemas, their governance, and now their meaning to finally live in one place. So if you have data and you want to know what it means, you ask Sourcemeta One.