{"slug": "solving-the-json-schema-dialect-fragmentation-problem-with-auto-upgrades", "title": "Solving the JSON Schema dialect fragmentation problem with auto-upgrades", "summary": "Sourcemeta has released tooling that automatically upgrades any JSON Schema to the dialect a given tool expects, via the JSON Schema CLI or the Sourcemeta One registry, addressing fragmentation across the standard's many published dialects. JSON Schema has shipped breaking changes across drafts since 2009 — for example, Draft 6 renamed `id` to `$id`, and `$anchor` accepted colons in 2019-09 but no longer does in 2020-12 — while specs such as OpenAPI layer their own extensions on top. Sourcemeta cites a 2025 OpenProceedings paper calling JSON Schema \"the de facto standard schema language to describe JSON data\" and notes every major AI provider (OpenAI, Google, Anthropic, Mistral, xAI, DeepSeek) and the Model Context Protocol have converged on it, with every EU country required to run eIDAS 2.0 digital identity on it from December 2026.", "body_md": "TL;DR: JSON Schema has published many dialects with breaking changes, and specs like OpenAPI add their own extensions and constraints on top. Different tools and schemas sit on different dialects, so using a schema you need or plugging it into a tool you need is often hard or impossible. What you really want is to keep one schema and let every tool receive it in the dialect it understands. Sourcemeta finally makes that possible, upgrading any schema on the fly to the dialect a tool expects, through the JSON Schema CLI or the Sourcemeta One registry.\n\n## JSON Schema evolves, and evolution brings breaking changes\n\nA 2025 OpenProceedings paper calls JSON Schema *“the de facto standard schema\nlanguage to describe JSON data”*, with an adoption that is *“almost\nubiquitous”* ([OpenProceedings,\n2025](https://openproceedings.org/2025/conf/edbt/paper-T3.pdf)). Today, it is\n[the one schema language every major AI provider (OpenAI, Google, Anthropic,\nMistral, xAI, DeepSeek) has converged on](https://www.sourcemeta.com/blog/ai-only-speaks-json-schema/), and the format the Model Context\nProtocol speaks. It is what [every G7 government runs its digital public\ninfrastructure on](https://www.sourcemeta.com/blog/g7-runs-on-json-schema/), and from\nDecember 2026 every EU country must run its [eIDAS 2.0 digital\nidentity](https://github.com/EWC-consortium/eudi-wallet-rulebooks-and-schemas/tree/main/data-schemas)\nsystem on it as well. Whatever you are building, you almost certainly depend\non JSON Schema, directly or [through OpenAPI](https://www.sourcemeta.com/blog/json-schema-dominates-openapi/), MCP, or a cloud service.\n\nLike any healthy standard, JSON Schema evolves. Each new version revisits\nearlier decisions as the community learns more, rather than keeping them\nforever. That willingness to break backward compatibility is much of what has\nkept it from accumulating legacy it could never remove. The price is simply\nthat a schema written for one version may no longer be valid, or may quietly\nmean something different, under another. Some are easy to spot: in Draft 6 the\nidentifier keyword [`id`](https://www.learnjsonschema.com/draft4/core/id/) was\nrenamed to [`$id`](https://www.learnjsonschema.com/draft6/core/id/). Others\nare far more subtle, like how the\n[`$anchor`](https://www.learnjsonschema.com/2020-12/core/anchor/) keyword\n[accepted colons in\n2019-09](https://www.learnjsonschema.com/2019-09/core/anchor/) but [no longer\ndoes in 2020-12](https://www.learnjsonschema.com/2020-12/core/anchor/).\n\nThe result is a family of dialects, each with its own set of breaking changes:\n\n| Dialect | Year | Breaking changes | \n|---|---|---|\n| Draft 0 | 2009 | The first published draft. | \n| Draft 1 | 2009 | No schema changes (an IETF metadata revision). | \n| Draft 2 | 2010 | `maxDecimal` replaced by[`divisibleBy`](https://www.learnjsonschema.com/draft3/core/divisibleby/) , redefined from decimal places to divisibility. | \n| Draft 3 | 2010 | `optional` replaced by[`required`](https://www.learnjsonschema.com/draft3/core/required/) with inverted meaning.`maximumCanEqual` /`minimumCanEqual` replaced by[`exclusiveMaximum`](https://www.learnjsonschema.com/draft3/core/exclusivemaximum/) /[`exclusiveMinimum`](https://www.learnjsonschema.com/draft3/core/exclusiveminimum/) .`requires` renamed to[`dependencies`](https://www.learnjsonschema.com/draft3/core/dependencies/) .[`format`](https://www.learnjsonschema.com/draft3/core/format/) dropped the address-oriented values`street-address` ,`locality` ,`region` ,`postal-code` and`country` . | \n| Draft 4 | 2013 | [`required`](https://www.learnjsonschema.com/draft4/validation/required/) changed from a per-property boolean flag to an array of names.[`disallow`](https://www.learnjsonschema.com/draft3/core/disallow/) and[`extends`](https://www.learnjsonschema.com/draft3/core/extends/) removed.[`divisibleBy`](https://www.learnjsonschema.com/draft3/core/divisibleby/) renamed to[`multipleOf`](https://www.learnjsonschema.com/draft4/validation/multipleof/) .[`type`](https://www.learnjsonschema.com/draft4/validation/type/) dropped the`\"any\"` value and can no longer hold inline subschemas.[`format`](https://www.learnjsonschema.com/draft4/validation/format/) was pruned to six values (`date` ,`time` ,`utc-millisec` ,`regex` ,`color` ,`style` and`phone` removed, with`ip-address` and`host-name` renamed to`ipv4` and`hostname` ). | \n| Draft 5 | 2016 | No schema changes (a cleanup of Draft 4 that kept its meta-schemas). | \n| Draft 6 | 2017 | [`id`](https://www.learnjsonschema.com/draft4/core/id/) renamed to[`$id`](https://www.learnjsonschema.com/draft6/core/id/) . Boolean[`exclusiveMinimum`](https://www.learnjsonschema.com/draft6/validation/exclusiveminimum/) /[`exclusiveMaximum`](https://www.learnjsonschema.com/draft6/validation/exclusivemaximum/) became numeric.[`type`](https://www.learnjsonschema.com/draft6/validation/type/)`\"integer\"` now accepts values like`1.0` . | \n| Draft 7 | 2018 | None. Backward-compatible with Draft 6. | \n| 2019-09 | 2019 | [`definitions`](https://www.learnjsonschema.com/draft7/validation/definitions/) renamed to[`$defs`](https://www.learnjsonschema.com/2019-09/core/defs/) .[` dependencies`](https://www.learnjsonschema.com/draft7/validation/dependencies/) split into[`dependentRequired`](https://www.learnjsonschema.com/2019-09/validation/dependentrequired/) and[`dependentSchemas`](https://www.learnjsonschema.com/2019-09/applicator/dependentschemas/) . Plain-name anchors moved from an[`$id`](https://www.learnjsonschema.com/2019-09/core/id/) fragment to[`$anchor`](https://www.learnjsonschema.com/2019-09/core/anchor/) , and[`$ref`](https://www.learnjsonschema.com/2019-09/core/ref/) may now sit beside other keywords. | \n| 2020-12 | 2020 | Array [`items`](https://www.learnjsonschema.com/2019-09/applicator/items/) and[`additionalItems`](https://www.learnjsonschema.com/2019-09/applicator/additionalitems/) became[`prefixItems`](https://www.learnjsonschema.com/2020-12/applicator/prefixitems/) and[`items`](https://www.learnjsonschema.com/2020-12/applicator/items/) .[`$recursiveRef`](https://www.learnjsonschema.com/2019-09/core/recursiveref/) /[`$recursiveAnchor`](https://www.learnjsonschema.com/2019-09/core/recursiveanchor/) replaced by[`$dynamicRef`](https://www.learnjsonschema.com/2020-12/core/dynamicref/) /[`$dynamicAnchor`](https://www.learnjsonschema.com/2020-12/core/dynamicanchor/) .[`contains`](https://www.learnjsonschema.com/2020-12/applicator/contains/) now marks matched items as evaluated, changing[`unevaluatedItems`](https://www.learnjsonschema.com/2020-12/unevaluated/unevaluateditems/) results.[`$anchor`](https://www.learnjsonschema.com/2020-12/core/anchor/) dropped the colon from its allowed characters. | \n\n## And those breaking changes cascade to JSON Schema’s adopters\n\nJSON Schema is also a building block for other specifications, which embed it to describe their data. So every breaking change in JSON Schema cascades up into whatever is built on top of it. The clearest example is OpenAPI, which embeds JSON Schema to describe the data in an API.\n\nWhat an OpenAPI document embeds is not a plain upstream dialect but its own variant of JSON Schema, the OpenAPI Schema Object. Each OpenAPI version ties that object to a particular JSON Schema dialect and then extends or restricts it in its own way. OpenAPI has also rebased onto a newer dialect more than once. All of this adds a new dimension to the fragmentation from the previous section:\n\n| Version | Year | JSON Schema dialect | \n|---|---|---|\n| Swagger 1.2 | 2014 | Reuses JSON Schema Draft 4’s primitive types ( `type` plus a`format` modifier), but its Models and Properties are Swagger’s own objects that each[*“follows a subset of the JSON-Schema specification”*](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/1.2.md) , with non-standard spellings like`defaultValue` (not[`default`](https://www.learnjsonschema.com/draft4/validation/default/) ) and`minimum` /`maximum` given as strings. | \n| Swagger / OpenAPI 2.0 | 2014 | [*“a subset of JSON Schema Draft 4”*](https://spec.openapis.org/oas/v2.0.html) with OpenAPI-only additions (`discriminator` ,`readOnly` ,`xml` ,`externalDocs` ,`example` ) and its own`format` values (`int32` ,`int64` ,`float` ,`double` ,`byte` ,`binary` ,`password` ).[`type`](https://www.learnjsonschema.com/draft4/validation/type/) must be single-valued, and only[`allOf`](https://www.learnjsonschema.com/draft4/validation/allof/) is supported among the logical applicators. | \n| OpenAPI 3.0.x | 2017 | [*“an extended subset of the JSON Schema Specification Wright Draft 00”*](https://spec.openapis.org/oas/v3.0.3.html) (Draft 4 lineage), adding`nullable` ,`discriminator` ,`readOnly` ,`writeOnly` ,`xml` ,`externalDocs` ,`example` ,`deprecated` and its own`format` values (`int32` ,`int64` ,`byte` ,`binary` ,`password` ).[`type`](https://www.learnjsonschema.com/draft4/validation/type/) must be a single string (no`null` , use`nullable` ), and any JSON Schema keyword it does not list is*“strictly unsupported”* . | \n| OpenAPI 3.1 | 2021 | [*“a superset of the JSON Schema Specification Draft 2020-12”*](https://spec.openapis.org/oas/v3.1.0.html) , defining an[OpenAPI Schema Object dialect](https://spec.openapis.org/oas/3.1/dialect/base) whose base vocabulary adds four keywords ([`discriminator`](https://www.learnjsonschema.com/2020-12/openapi-3.1/discriminator/) ,[`xml`](https://www.learnjsonschema.com/2020-12/openapi-3.1/xml/) ,[`externalDocs`](https://www.learnjsonschema.com/2020-12/openapi-3.1/externaldocs/) , and[`example`](https://www.learnjsonschema.com/2020-12/openapi-3.1/example/) ). | \n| OpenAPI 3.2 | 2025 | Also [a superset of 2020-12](https://spec.openapis.org/oas/v3.2.0.html) , with its[own base vocabulary](https://spec.openapis.org/oas/3.2/dialect/2025-09-17) carrying the same four keywords in revised form.[`example`](https://www.learnjsonschema.com/2020-12/openapi-3.2/example/) is now deprecated, and[`xml`](https://www.learnjsonschema.com/2020-12/openapi-3.2/xml/) gains a`nodeType` while deprecating`attribute` and`wrapped` . | \n\nOpenAPI is not the only one. The [Model Context\nProtocol](https://modelcontextprotocol.io), the fast-growing standard that\nwires AI models to tools and [also builds on JSON Schema](https://www.sourcemeta.com/blog/ai-only-speaks-json-schema/) to describe a tool’s inputs and\noutputs, hit the problem from the opposite direction. Where each OpenAPI\nversion pins a dialect and reshapes it, MCP left its embedded tool schemas\nwithout a declared dialect at all. Implementations then ended up *“split\nbetween draft-07 and 2020-12”*, until\n[SEP-1613](https://modelcontextprotocol.io/seps/1613-establish-json-schema-2020-12-as-default-dialect-f)\n(October 2025) *“establishes JSON Schema 2020-12 as the default dialect for\nembedded schemas within MCP messages”*.\n\nThe flagship API and AI specs are not the only ones to change dialect. For\nexample, format specifications that ship JSON Schemas straight to their users\nhave quietly done the same. [glTF](https://www.khronos.org/gltf/), Khronos’s\n3D asset format, went [from Draft 4 to\n2020-12](https://raw.githubusercontent.com/KhronosGroup/glTF/main/specification/2.0/schema/glTF.schema.json)\nin 2021. [Docker Compose](https://compose-spec.io/) moved [from 2019-09 to\n2020-12](https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json)\nin 2026. [SPDX](https://spdx.dev/), the software bill-of-materials standard,\nwent [from Draft 7 to\n2020-12](https://raw.githubusercontent.com/spdx/spdx-spec/develop/schemas/spdx-schema-3-0-1.json)\nwith its 3.0 line in 2024. And [STAC](https://stacspec.org/), the\nSpatioTemporal Asset Catalog for geospatial data, moved [from Draft 6 to Draft\n7](https://raw.githubusercontent.com/radiantearth/stac-spec/v0.9.0/catalog-spec/json-schema/catalog.json)\nin 2020. Whole products migrate too: [Confluent Schema\nRegistry](https://docs.confluent.io/platform/current/schema-registry/index.html),\nKafka’s schema registry, [added\n2020-12](https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/serdes-json.html)\nin Platform 7.6.0 in 2024, and [OpenMetadata](https://open-metadata.org/), an\nopen-source metadata platform, moved its entire schema corpus from Draft 7 to\n2020-12 in [a single 2026 pull\nrequest](https://github.com/open-metadata/OpenMetadata/pull/33631) touching\n1,324 files.\n\n## How fragmented is it, really?\n\nPinning this down precisely is close to impossible. Most JSON Schema and OpenAPI use lives inside companies, in private repositories and internal services no one outside can measure. What is public is only a slice, so the best we can do is triangulate from a few imperfect proxies. None is exhaustive or representative on its own, and each looks at a different population, so treat what follows as a rough picture, not a census. The useful part is that the proxies disagree on magnitude but agree on the ranking.\n\n### Live traffic to the official website\n\nStart with live traffic. The [json-schema.org](https://json-schema.org)\nwebsite hosts the meta-schema for each dialect. Its Cloudflare analytics show\nwhich of those meta-schemas the world requests most over a trailing 30-day\nwindow, and the ranking is telling: [Draft\n7](https://json-schema.org/draft-07/schema) leads by a wide margin, [Draft\n4](https://json-schema.org/draft-04/schema) is a close second, and\n[2020-12](https://json-schema.org/draft/2020-12/schema) only comes third, with\n[Draft 6](https://json-schema.org/draft-06/schema) and\n[2019-09](https://json-schema.org/draft/2019-09/schema) far behind. That Draft\n4, whose specification was published back in 2013, still outdraws the current\ndialect is the sharpest sign of how sticky older dialects can be.\n\nThe first surprise is that Draft 7 leads at all. OpenAPI, the single largest\nconsumer of JSON Schema, never used it. Its 2.0 and 3.0 lines sit on the Draft\n4 lineage, and 3.1 and later on 2020-12. Much of Draft 7’s traffic comes down\nto one library, the Python\n[`jsonschema`](https://github.com/python-jsonschema/jsonschema) library’s\n*[deprecated](https://github.com/python-jsonschema/jsonschema/pull/1049)*\nremote-`$ref` fetch path, which sends the User-Agent `python-jsonschema (deprecated $ref resolution)`. It fetches a schema’s meta-schema over the\nnetwork on every validation, doing exactly what the specification advises\nagainst: [*“implementations SHOULD NOT assume they should perform a network\noperation when they encounter a network-addressable\nURI”*](https://json-schema.org/draft/2020-12/json-schema-core.html#section-8.2.3).\nThat does not make Draft 7’s lead fake, though. The library only fetches the\nDraft 7 meta-schema because the schemas people feed it are Draft 7, so the\nusage is real. The deprecated path just tilts the analytics further in its\nfavour.\n\nThe other surprise is at the modern end. 2020-12 has been out for nearly six\nyears, and OpenAPI 3.1 adopted it back in February 2021. Yet it is still not\nthe most-requested dialect, and it has barely moved since [a 2023 article that\ninspected the same Cloudflare\nanalytics](https://json-schema.org/blog/posts/website-analytics-snapshot-2023).\nSome of that gap is measurement, since the modern tools that use 2020-12 are\nthe ones most likely to ship the meta-schemas as built-in copies and validate\nagainst those rather than fetch them over the network, so the figures\nunderstate it. But the persistence is also the point. That same\npython-jsonschema traffic was already there in 2023, which means whoever was\nvalidating those Draft 7 schemas then is still doing it now, and still has not\nupgraded.\n\nA last quirk we cannot explain is that 2020-12’s [core vocabulary\nmeta-schema](https://json-schema.org/draft/2020-12/meta/core) is requested\nnearly twice as often as the [dialect\nmeta-schema](https://json-schema.org/draft/2020-12/schema) that references it.\n\n### What developers publish on GitHub\n\nWhat developers actually declare on GitHub tells a compatible story. Every dialect’s meta-schema lives at a distinct URL, and each version of OpenAPI and AsyncAPI leaves a distinct marker in its documents. So a text search for those strings is a rough headcount of what people publish.\n\nThe table below runs one such query per row against GitHub’s code search API on September 28th, 2026, and links each one so you can re-run it. This is text matching over the API, not schema parsing. It counts every file containing the string, including generated and vendored copies, which inflates the newest dialects most. GitHub also returns the totals as estimates. So treat the numbers as approximate, good enough as one more proxy, where the ranking matters more than any exact figure.\n\n| Specification | Version | Files on GitHub | Searched for | \n|---|---|---|---|\n| JSON Schema | Draft 0 | 398 | [`\"json-schema.org/draft-00/schema\"`](https://github.com/search?q=%22json-schema.org%2Fdraft-00%2Fschema%22&type=code) | \n| JSON Schema | Draft 1 | 487 | [`\"json-schema.org/draft-01/schema\"`](https://github.com/search?q=%22json-schema.org%2Fdraft-01%2Fschema%22&type=code) | \n| JSON Schema | Draft 2 | 489 | [`\"json-schema.org/draft-02/schema\"`](https://github.com/search?q=%22json-schema.org%2Fdraft-02%2Fschema%22&type=code) | \n| JSON Schema | Draft 3 | 28,288 | [`\"json-schema.org/draft-03/schema\"`](https://github.com/search?q=%22json-schema.org%2Fdraft-03%2Fschema%22&type=code) | \n| JSON Schema | Draft 4 | 444,416 | [`\"json-schema.org/draft-04/schema\"`](https://github.com/search?q=%22json-schema.org%2Fdraft-04%2Fschema%22&type=code) | \n| JSON Schema | Draft 6 | 194,560 | [`\"json-schema.org/draft-06/schema\"`](https://github.com/search?q=%22json-schema.org%2Fdraft-06%2Fschema%22&type=code) | \n| JSON Schema | Draft 7 | 839,680 | [`\"json-schema.org/draft-07/schema\"`](https://github.com/search?q=%22json-schema.org%2Fdraft-07%2Fschema%22&type=code) | \n| JSON Schema | 2019-09 | 63,744 | [`\"json-schema.org/draft/2019-09/schema\"`](https://github.com/search?q=%22json-schema.org%2Fdraft%2F2019-09%2Fschema%22&type=code) | \n| JSON Schema | 2020-12 | 829,440 | [`\"json-schema.org/draft/2020-12/schema\"`](https://github.com/search?q=%22json-schema.org%2Fdraft%2F2020-12%2Fschema%22&type=code) | \n| OpenAPI | 2.0 (Draft 4 lineage) | 67,328 | [`\"swagger: \\\"2.0\\\"\"`](https://github.com/search?q=%22swagger%3A%20%5C%222.0%5C%22%22&type=code) | \n| OpenAPI | 3.0 (Draft 4 subset) | 307,200 | [`\"openapi: 3.0\"`](https://github.com/search?q=%22openapi%3A%203.0%22&type=code) | \n| OpenAPI | 3.1 (2020-12) | 100,352 | [`\"openapi: 3.1\"`](https://github.com/search?q=%22openapi%3A%203.1%22&type=code) | \n| OpenAPI | 3.2 (2020-12) | 1,824 | [`\"openapi\": \"3.2.0\"`](https://github.com/search?q=%22openapi%22%3A%20%223.2.0%22&type=code) | \n| AsyncAPI | 2.x + 3.0 (Draft 7) | 13,088 | [`\"asyncapi: 2\"`](https://github.com/search?q=%22asyncapi%3A%202%22&type=code) ,[`\"asyncapi: 3\"`](https://github.com/search?q=%22asyncapi%3A%203%22&type=code) | \n\nDraft 7 leads here too, now by published files rather than by requests. The one real divergence is 2020-12, which ranks higher by file count than by traffic. Even that reflects the measurement bias pulling each way. File counts are inflated by generated and vendored copies, while the traffic figures understate 2020-12 because modern tools ship the meta-schemas as built-in copies instead of fetching them.\n\nDraft 4 is the other constant. It no longer leads by raw file count, but its lineage is what most public APIs still run on: OpenAPI 2.0 and 3.0 are both custom Draft 4 dialects, and together they still outnumber the 2020-12-aligned OpenAPI 3.1 by a wide margin, years after 2020-12 shipped. The extremes behave as expected, with the earliest drafts effectively extinct and OpenAPI 3.2, released only in September 2025, barely registering yet.\n\nA study from 2020 shows how recent Draft 7’s rise really is. *[An Empirical\nStudy on the “Usage of Not” in Real-World JSON Schema\nDocuments](https://arxiv.org/abs/2107.08677)* crawled GitHub in July 2020,\nremoved duplicate copies, and found that across 82,094 files Draft 4 was\ndeclared by 78.85% and Draft 7 by only 2.67%, with Draft 6 and Draft 3 in\nbetween. That makes sense for the time. 2020-12 was not out yet, and Draft 7\nwas only two years old. Draft 4 was still the clear default. Our own numbers,\nwhere Draft 7 now leads, show how far it has come since 2020.\n\n### The SchemaStore catalogue\n\nOne curated catalogue is worth a closer look.\n[SchemaStore](https://github.com/SchemaStore/schemastore) is a large public\ncollection of JSON schemas for common configuration files. It is created and\nmaintained by Mads Kristensen, [a longtime Visual Studio program manager at\nMicrosoft](https://www.youtube.com/watch?v=-yYTxLZZk58). It is also\noverwhelmingly Draft 7. Of the 977 standalone schemas we analysed on September\n28th, 2026, 89.3% declare Draft 7, only 7.4% declare 2020-12, just 1.1% Draft\n4, and 1.7% declare no dialect at all.\n\nThat is the mirror image of the OpenAPI-driven proxies, where Draft 4 and 2020-12 lead and Draft 7 barely featured. The difference is what SchemaStore is for. Visual Studio and Visual Studio Code draw on it for JSON and YAML auto-completion. It lives entirely in the configuration-and-editor world, which has no OpenAPI in it, so Draft 7 dominates. This suggests that much of Draft 7’s lead in the earlier proxies comes from this world rather than from APIs, and it is not a small one.\n\n## What dialects do real products and specs actually use?\n\nSo far we have counted schemas. The following table instead takes real adopters of JSON Schema, commercial products, open-source projects, and standards whose published artefact is the schema itself, and groups them by the dialect each one uses. It is not exhaustive, and it reflects what each project’s own documentation or published schemas state at the time of writing. Adopters that support several dialects appear in every row they belong to, so the numbers are row entries, not unique adopters.\n\nThe pattern is that no single dialect wins. Real, current adopters span four eras at once, from Draft 3 to 2020-12, and every one of them is still a production dependency for someone. Draft 4 and Draft 7 carry the longest tails, especially on the commercial side, while 2019-09 was skipped almost entirely and 2020-12 is the one clearly gaining ground, mostly among newer AI and data products. The split is sharp even inside a single category: among API gateways alone, Kong, AWS API Gateway, Apache APISIX, Gravitee, and Axway validate against Draft 4, NGINX App Protect reaches Draft 7, Zuplo is on 2020-12, and KrakenD supports several at once, so even a task as narrow as validating requests in a gateway has no one-dialect answer. The LLM structured-output APIs are worse still, each accepting its own incompatible subset that is not a clean dialect at all.\n\n## Where fragmentation bites\n\nThe fragmentation is not a theoretical worry. It surfaces as a steady stream of GitHub issues, and the same failures show up from one project to the next, often with similar error strings. Here is what it looks like, one trap at a time.\n\n### The hard reject\n\nA tool that supports exactly one dialect does not adapt to a schema written in\nanother. It rejects it outright, before any validation happens, so the\nmismatch surfaces as a flat refusal rather than a wrong answer. The clearest\nlive example is the Model Context Protocol, where servers built with the [MCP\nTypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) tag\ntheir tool schemas as Draft 7 while Claude’s client validates against 2020-12\nand nothing else.\n\nThe schema is discarded before a single request is made, with an error that names the exact problem:\n\nTool ‘<name>’ has an invalid outputSchema: JSON Schema declares an\nunsupported dialect (\"$schema\": “[http://json-schema.org/draft-07/schema#\"](http://json-schema.org/draft-07/schema#%22)).\nThe default validator supports JSON Schema 2020-12 only\n\nThat one report drew 54 comments ([claude-code\n#86142](https://github.com/anthropics/claude-code/issues/86142)), and the\ncause is mundane. The SDK *“falls back to ‘draft-7’ whenever no explicit\ntarget is passed”* ([typescript-sdk\n#2721](https://github.com/modelcontextprotocol/typescript-sdk/issues/2721)).\n\nThe same error shows up in other MCP projects too, like the reference file\nserver ([servers\n#4841](https://github.com/modelcontextprotocol/servers/issues/4841)) and the\n[mcp-use](https://github.com/mcp-use/mcp-use) client ([mcp-use\n#1839](https://github.com/mcp-use/mcp-use/issues/1839)). But this is not an\nMCP problem. It is what any single-dialect tool does.\n[AJV](https://ajv.js.org) defaults to Draft 7, so feed it a 2020-12 schema and\nit fails with `no schema with key or ref` unless you pass `--spec=draft2020`\n([ajv-cli #251](https://github.com/ajv-validator/ajv-cli/issues/251)). And\nsome tools never made the jump at all, like\n[react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form),\nwhich still cannot handle 2020-12 ([rjsf\n#3750](https://github.com/rjsf-team/react-jsonschema-form/issues/3750), [rjsf\n#3751](https://github.com/rjsf-team/react-jsonschema-form/issues/3751)).\n\nThe same loud failure happens keyword by keyword, not only for a schema as a\nwhole.\n[`exclusiveMinimum`](https://www.learnjsonschema.com/2020-12/validation/exclusiveminimum/)\nflipped from a boolean flag to a numeric bound between Draft 4 and Draft 6, so\na validator built for one form errors on the other ([kogosoftwarellc\n#776](https://github.com/kogosoftwarellc/open-api/issues/776)). Array\n[`items`](https://www.learnjsonschema.com/draft4/validation/items/) became\n[`prefixItems`](https://www.learnjsonschema.com/2020-12/applicator/prefixitems/)\nin 2020-12, crashing generators that still expect an `items` key, with\n[orval](https://github.com/orval-labs/orval) dying on `All arrays must have an items key define` ([orval\n#890](https://github.com/orval-labs/orval/issues/890)) and\n[openapi-generator](https://github.com/OpenAPITools/openapi-generator) hitting\nthe same wall twice ([openapi-generator\n#19704](https://github.com/OpenAPITools/openapi-generator/issues/19704),\n[openapi-generator\n#17923](https://github.com/OpenAPITools/openapi-generator/issues/17923)). And\nkeywords like\n[`minContains`](https://www.learnjsonschema.com/2020-12/validation/mincontains/)\nor [`$anchor`](https://www.learnjsonschema.com/2020-12/core/anchor/) are\nflagged “unknown” under the wrong dialect ([AJV\n#1872](https://github.com/ajv-validator/ajv/issues/1872), [AJV\n#1854](https://github.com/ajv-validator/ajv/issues/1854)).\n\n### The silent trap\n\nWorse than a loud rejection is no error at all. A dialect change can render a\nonce-valid keyword unknown, sometimes changing a schema’s meaning by accident.\nAs JSON Schema TSC member Ben Hutton puts it, an unknown keyword [“is just\ngoing to be ignored. You won’t get an error. It makes no noise.\nSilence.”](https://words.benhutton.me/2025-01-20-openai-chatgpt-and-structured-output-with-json-schema-pt1)\n\nThe move from OpenAPI 3.0 to 3.1 is full of these. `nullable` was a real\nkeyword in 3.0 but does not exist in 2020-12, so a 3.1 document that still\nemits it simply loses that information:\n\nnullable: true is emitted as-is. OpenAPI 3.1 uses JSON Schema 2020-12, where nullable no longer exists … Tools reading a 3.1 document ignore it, so the nullability is silently lost.\n\nThe same silent drop runs through all 579 `nullable` occurrences in\n[Svix](https://www.svix.com)’s published spec ([svix\n#2551](https://github.com/svix/svix-webhooks/issues/2551), [fastify-swagger\n#954](https://github.com/fastify/fastify-swagger/issues/954)). Other changes\nare just as quiet. An\n[`exclusiveMinimum`](https://www.learnjsonschema.com/2020-12/validation/exclusiveminimum/)\nbound can vanish on upgrade, *“left as `undefined`”* with nothing flagged\n([scalar #10235](https://github.com/scalar/scalar/issues/10235)). A bare\n`type: \"null\"` can conjure a phantom model in a code generator\n([openapi-generator\n#24520](https://github.com/OpenAPITools/openapi-generator/issues/24520)). In\n[n8n](https://n8n.io), the same Draft 7 versus 2020-12 mismatch from the hard\nreject above never surfaces. The MCP server’s tool schemas are discarded for\ndeclaring the wrong dialect, and the only sign is *“zero tools appear in chat,\nwith no visible error”* ([n8n\n#36361](https://github.com/n8n-io/n8n/issues/36361)).\n\n### The mixed-dialect trap\n\n[JSON Schema was designed to let a single document mix\ndialects](https://json-schema.org/draft/2020-12/json-schema-core.html). So a\n2020-12 document that references a Draft 7 GeoJSON schema is spec-valid. The\ntools, though, often disagree. For example, reference a Draft 7 schema from a\n2020-12 document, and a tool may apply the newer dialect across the reference,\nwhere a keyword like\n[`dependencies`](https://www.learnjsonschema.com/draft7/validation/dependencies/)\nno longer exists. The [CoverageJSON](https://covjson.org) maintainers hit\nexactly this wall:\n\nOpenAPI 3.1 base their schema on JSON Schema Specification Draft 2020-12 …\nBut [https://covjson.org/schema/dev/coveragejson.json](https://covjson.org/schema/dev/coveragejson.json) is based on\n[http://json-schema.org/draft-07/schema](http://json-schema.org/draft-07/schema), which is an older version of\nJSON-Schema.\n\nThe thread ran to 24 comments before it was understood ([CoverageJSON\n#205](https://github.com/opengeospatial/CoverageJSON/issues/205)).\n\nThe same failure shows up wherever a cross-dialect reference is bundled or\nresolved. [Redocly](https://github.com/Redocly/redocly-cli) trips on bundling\na 2020-12 root into an OpenAPI 3.1 spec ([redocly-cli\n#1613](https://github.com/Redocly/redocly-cli/issues/1613)),\n[json-schema-ref-parser](https://github.com/APIDevTools/json-schema-ref-parser)\non the [`$id`](https://www.learnjsonschema.com/2020-12/core/id/) and\n[`$ref`](https://www.learnjsonschema.com/2020-12/core/ref/) changes from\n2019-09 ([json-schema-ref-parser\n#145](https://github.com/APIDevTools/json-schema-ref-parser/issues/145)),\n[python-jsonschema](https://github.com/python-jsonschema/jsonschema) on an\nembedded 2020-12 meta-schema ([python-jsonschema\n#884](https://github.com/python-jsonschema/jsonschema/issues/884)) and on a\nresolver rewrite that broke a working STAC schema ([python-jsonschema\n#1064](https://github.com/python-jsonschema/jsonschema/issues/1064)).\n\n### Why multi-dialect tooling is not enough\n\nSo the obvious fix suggests itself: make every tool speak every dialect. Many already try, since general-purpose validators tend to support several dialects at once. But that alone cannot close the gap. Even a perfectly multi-dialect validator has no say over what happens next, as your schema flows on into someone else’s gateway, code generator, editor, and LLM endpoint. There will always be one more consumer, or one more publisher, working in a different dialect than you expected. Making every tool speak every dialect is the right idea in principle and impossible to guarantee in practice.\n\n## Auto-upgrade the schema, and why nobody has finished the job\n\nThe holy grail is easy to state. You write a schema once, in whatever dialect was current at the time, and from then on your tooling transforms it on the fly into whatever dialect each consumer needs, including dialects that have not been published yet. The endless question of which dialect to write in, which dialect to support, which dialect a given tool happens to speak, simply disappears for good.\n\nThis is what Sourcemeta’s\n[auto-upgrade](https://github.com/sourcemeta/jsonschema/blob/main/docs/upgrade.markdown)\ndoes. The idea itself is not new, and people have written dialect upgraders\nfor years, but none of them gets the whole job right.\n\n| Capability | [json-schema-migrate](https://github.com/ajv-validator/json-schema-migrate) | [json-schema-migrate-x](https://github.com/un-ts/json-schema-migrate-x) | [jsonschema-migrate](https://crates.io/crates/jsonschema-migrate) (Rust) | **[Sourcemeta](https://www.sourcemeta.com)** | \n|---|---|---|---|---|\n| Repoints [`$ref`](https://www.learnjsonschema.com/2020-12/core/ref/) after a keyword moves | No | No | Partial (only `#/definitions` ) | **Yes** | \n| Fixes [`$anchor`](https://www.learnjsonschema.com/2020-12/core/anchor/) references | No (emits invalid names) | No (emits invalid names) | No (deletes anchors) | **Yes** | \n| Guards new keywords | No | No | No | **Yes** | \n| Embedded resources | Yes | Yes | No | **Yes** | \n| Mixed-dialect documents | No (force one dialect) | No (force one dialect) | No (inner left unconverted) | **Yes** | \n| Handles a stray `$schema` (invalid without`$id` ) | No (leaves it) | No (rewrites it to the 2020-12 URI) | No (leaves it) | **Yes** | \n| Emits the requested dialect | No (writes 2019-09) | Yes | Yes | **Yes** | \n| Source dialects | Draft 4 | Draft 4 | Draft 4 to 2019-09 | **Draft 3 to 2020-12, and OpenAPI 3.0/3.1** | \n| Target dialects | Draft 6 to 2020-12 | Draft 6 to 2020-12 | 2020-12 only | **Draft 4 to 2020-12, and OpenAPI 3.1/3.2** | \n| OpenAPI 3.0 schemas | No | No | No | **Yes** | \n| OpenAPI 3.1 / 3.2 custom vocabularies | No | No | No | **Yes** | \n| Maintained | No (2021) | Yes | Yes | **Yes** | \n\n**Note:** tools that only upgrade schemas inside an OpenAPI document, such\nas [Scalar’s\nopenapi-upgrader](https://www.npmjs.com/package/@scalar/openapi-upgrader),\n[swagger2openapi](https://github.com/Mermade/oas-kit), and\n[openapi-down-convert](https://github.com/apiture/openapi-down-convert), are\nleft out of the table above, since none can take a standalone JSON Schema.\n\nJSON Schema is a [complex language](https://arxiv.org/abs/2307.10034), and\nupgrading it correctly across dialects is a massive undertaking. The table\nabove is a non-exhaustive sample of the aspects that make it hard, and there\nare many more, some extremely subtle, like the\n[`$anchor`](https://www.learnjsonschema.com/2020-12/core/anchor/) keyword\nquietly dropping the colon from its legal characters between 2019-09 and\n2020-12. Sourcemeta is committed to getting all of them right, every time.\n\n## Using it today\n\nThere are two ways to get a schema in the dialect a given tool expects, and they suit different workflows. The command line is for local development, where you upgrade a schema yourself when you need it. The registry is for publishing at an organisation’s scale, where you store one schema and let each consumer ask for the dialect it wants, so nobody upgrades anything by hand.\n\n### The JSON Schema CLI\n\nThe [`jsonschema upgrade`](https://github.com/sourcemeta/jsonschema/blob/main/docs/upgrade.markdown)\ncommand upgrades a schema on the spot. Point it at a file and name the target\ndialect. Take this Draft 3 schema, which embeds a Draft 7 resource of its own:\n\n```\n{\n  \"$schema\": \"http://json-schema.org/draft-03/schema#\",\n  \"id\": \"https://schemas.my-corp.com/order\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"quantity\": { \"type\": \"integer\", \"divisibleBy\": 5, \"required\": true },\n    \"express\": { \"$ref\": \"#/dependencies/creditCard\" },\n    \"shipTo\": {\n      \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n      \"$id\": \"https://schemas.my-corp.com/address\",\n      \"type\": \"object\",\n      \"dependencies\": { \"number\": [ \"street\" ] }\n    }\n  },\n  \"dependencies\": {\n    \"creditCard\": { \"required\": [ \"billingAddress\" ] }\n  }\n}\n```\n\nRun:\n\n```\njsonschema upgrade order.json --to 2020-12\n```\n\nand you get:\n\n```\n{\n  \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n  \"$id\": \"https://schemas.my-corp.com/order\",\n  \"type\": \"object\",\n  \"required\": [ \"quantity\" ],\n  \"properties\": {\n    \"quantity\": { \"type\": \"integer\", \"multipleOf\": 5 },\n    \"express\": { \"$ref\": \"#/dependentSchemas/creditCard\" },\n    \"shipTo\": {\n      \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n      \"$id\": \"https://schemas.my-corp.com/address\",\n      \"type\": \"object\",\n      \"dependentRequired\": { \"number\": [ \"street\" ] }\n    }\n  },\n  \"dependentSchemas\": {\n    \"creditCard\": { \"required\": [ \"billingAddress\" ] }\n  }\n}\n```\n\nIn one pass it took the outer schema from Draft 3 to 2020-12. It renamed\n[`id`](https://www.learnjsonschema.com/draft3/core/id/) to\n[`$id`](https://www.learnjsonschema.com/2020-12/core/id/), moved the\nper-property\n[`required`](https://www.learnjsonschema.com/2020-12/validation/required/)\nflag up into an array, and turned\n[`divisibleBy`](https://www.learnjsonschema.com/draft3/core/divisibleby/) into\n[`multipleOf`](https://www.learnjsonschema.com/2020-12/validation/multipleof/).\nEach use of\n[`dependencies`](https://www.learnjsonschema.com/draft3/core/dependencies/)\nsplit by form, the schema-valued one into\n[`dependentSchemas`](https://www.learnjsonschema.com/2020-12/applicator/dependentschemas/)\nand the property list into\n[`dependentRequired`](https://www.learnjsonschema.com/2020-12/validation/dependentrequired/).\nThe [`$ref`](https://www.learnjsonschema.com/2020-12/core/ref/) in `express`\nrepointed to match. The embedded `shipTo` resource is on a different dialect,\nDraft 7. It was upgraded on its own terms in the same pass.\n\n### Sourcemeta One\n\nThe command line is fine for a one-off, or a script that walks a directory. But an organisation can hold thousands of schemas across many dialects, owned by different teams and changing all the time. Upgrading each one by hand does not scale, and neither does keeping a converted copy for every consumer.\n\nSay the `order` schema above lives in a [Sourcemeta\nOne](https://one.sourcemeta.com) instance at `schemas.my-corp.com`. You choose\nthe dialect you want with an `as` query parameter on the fetch URL. A team\nstill on an older validator asks for Draft 7:\n\n```\nhttps://schemas.my-corp.com/order?as=draft7\n```\n\nAnother team on a modern stack asks the same path for 2020-12:\n\n```\nhttps://schemas.my-corp.com/order?as=2020-12\n```\n\nOne schema is stored, and each consumer gets the dialect it asks for. Those URLs are ordinary references too, so they drop into whatever consumes the schema. An OpenAPI 3.1 description can point a request body straight at the 2020-12 form:\n\n```\nopenapi: 3.1.0\ninfo:\n  title: Orders\n  version: 1.0.0\npaths:\n  /orders:\n    post:\n      requestBody:\n        content:\n          application/json:\n            schema:\n              $ref: \"https://schemas.my-corp.com/order?as=2020-12\"\n```\n\nA conversion always arrives bundled, so every reference inside travels already\nconverted. A consumer never follows a\n[`$ref`](https://www.learnjsonschema.com/2020-12/core/ref/) into a document\nleft in the old dialect. The full contract is in the [Sourcemeta One HTTP\nAPI](https://one.sourcemeta.com/api/#fetch).\n\n## Next steps\n\n**Custom meta-schemas.** Auto-upgrade covers the official dialects, but not a\nmeta-schema or a schema that declares a custom one. A meta-schema describes a\ndialect by naming that dialect’s keywords as ordinary data, often under\n[`properties`](https://www.learnjsonschema.com/2020-12/applicator/properties/)\nor\n[`patternProperties`](https://www.learnjsonschema.com/2020-12/applicator/patternproperties/).\nUpgrading renames the keywords in a schema, but it cannot tell which of those\ndata strings also meant a keyword and should move with them. So a conversion\ncan succeed and still leave the result out of step with the meta-schema, and\nthe meta-schema with its vocabularies. We are exploring meta-schema-level\nhints to carry the intent that cannot be inferred today.\n\n**Downgrades.** Auto-upgrade only goes one way today. Going the other way is\nharder, since a newer dialect can often express things an older one cannot.\nOur [preliminary\nresearch](https://github.com/json-schema-org/upgrade-downgrade-rules), from a\n[Google Summer of Code\nproject](https://github.com/json-schema-org/community/issues/599), found that\nmore cases are possible than expected.\n\n**Swagger 2.0.** Auto-upgrade starts at OpenAPI 3.0, not the older [Swagger\n2.0](https://spec.openapis.org/oas/v2.0.html). There has been almost no demand\nfor it, and the numbers above show it trailing OpenAPI 3.0 several times over.\nWe will consider adding it if enough people ask.\n\nThe specification is also trying to end new fragmentation at the source. Its\nnext release, JSON Schema v1, makes [backward compatibility a\npriority](https://json-schema.org/blog/posts/stable-json-schema) after [one\nlast breaking change](https://json-schema.org/blog/posts/the-last-breaking-change).\nBut that is the long game, and the dialects already deployed are not going\nanywhere, as the numbers above show. Auto-upgrade is for the fragmentation you\nhave now.", "url": "https://wpnews.pro/news/solving-the-json-schema-dialect-fragmentation-problem-with-auto-upgrades", "canonical_source": "https://www.sourcemeta.com/blog/json-schema-dialect-auto-upgrade/", "published_at": "2026-10-30 20:49:12+00:00", "updated_at": "2026-10-30 21:16:58.369510+00:00", "lang": "en", "topics": ["structured-data", "agent-protocols", "developer-tools", "ai-tools"], "entities": ["Sourcemeta", "JSON Schema", "OpenAPI", "Model Context Protocol", "OpenAI", "Google", "Anthropic", "Mistral"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/solving-the-json-schema-dialect-fragmentation-problem-with-auto-upgrades", "markdown": "https://wpnews.pro/news/solving-the-json-schema-dialect-fragmentation-problem-with-auto-upgrades.md", "text": "https://wpnews.pro/news/solving-the-json-schema-dialect-fragmentation-problem-with-auto-upgrades.txt", "jsonld": "https://wpnews.pro/news/solving-the-json-schema-dialect-fragmentation-problem-with-auto-upgrades.jsonld"}}