Blog · Machine readability

JSON-LD mistakes that quietly break parsing

Published 11 February 2026 · Updated 12 October 2026 · 9 min read

JSON-LD mistakes that quietly break parsing
Photograph: Frames For Your Heart / unsplash

The short answer: Loud JSON-LD errors are the easy ones. The dangerous ones are silent: a property name no context maps, a second node reusing an @id, a reference pointing at nothing, a block injected after the parser has already read the page. None of these throw. The markup validates, the page ships, and the parts you cared about were thrown away before anyone looked.

That gap between "no error" and "actually read" is where schema work tends to go to die.

Two failure classes, and only one of them shouts

Google's unparsable structured data report lists seventeen distinct error types, counted from the table on its help page when we read it on 12 October 2026, and that page is blunt that every item in the report is critical, with no warnings and no valid rows. Read the table and a pattern shows up fast. Thirteen of the seventeen are pure JSON syntax: a missing colon, a missing brace, a bad escape sequence, a truncated Unicode character. Those are the loud failures, and a linter catches them before the branch merges.

The remaining four are different animals. *Duplicate unique property*, *invalid top level element*, *incorrect value type* and *reference to nonexistent item* are all cases where the JSON parses perfectly well and the meaning is still broken. Those four are the ones worth your afternoon, with one caveat on the last of them that the section below spells out.

The quiet one: a key that does not expand is ignored

This mechanism is easy to skip, because nothing in the usual toolchain prompts you to look for it, and it comes straight out of the specification rather than anyone's blog. JSON-LD processing starts with expansion, where every key in the document is resolved against the active context into an absolute IRI. The W3C JSON-LD 1.1 spec states the consequence in one line: JSON keys that do not expand to an IRI are not Linked Data and are ignored when processed.

Ignored. Not flagged, not warned about, not reported. If you write datepublished instead of datePublished, schema.org has no term by that name, the key fails to expand, and it vanishes during expansion. Same for aggregaterating, sameas, mainEntityofPage and every other near-miss a human types at the end of a long day. JSON-LD is case sensitive on property names and schema.org is camelCase throughout, so the failure surface is wide.

The same applies to anything you invented. Teams add internalId or cmsVersion to the node for their own plumbing, then wonder why a validator reports fewer properties than the file contains. Those terms are not in the vocabulary, so they never make it past step one.

Two conditions make this worse than a typo normally is. The first is that a validator scoped to required properties has nothing to report when the misspelled property was an optional one, so the node comes back clean. The second is that the richest properties are usually the optional ones, which means a silent drop costs you exactly the detail you added schema for in the first place.

Collisions: @id reuse and dangling references

An @id is a name, not a label. Two nodes carrying the same @id are, by definition, the same node. The JSON-LD 1.1 specification says node objects may spread over several parts of a document and that the keys of those node objects are then merged into the properties of one resulting node. Where the two disagree on a property, something has to give, and the markup does not tell you which value survives.

This happens most often with templated sites. The layout emits an Organization node with @id ending #organization, a page template emits another for a different purpose, and both land in the same document under the same identifier. The merge is where entity facts go wrong, which is the problem described in Organization schema done properly arriving through a different door.

The inverse failure is the dangling cross-reference. You write "publisher": {"@id": "https://example.com/#org"}, the node with that @id lives in a second script block that only renders on the homepage, and on every other URL the reference points at nothing. A reference to an absent node is not an error in JSON and it is not an error in RDF either. It is simply a node you know nothing about, so a parser has nothing to complain about. Google's report does carry an error called *reference to nonexistent item*, but read its description before you rely on it: it is about a microdata itemref attribute pointing at a missing identifier, not about an unresolved @id in JSON-LD. We know of no public report that tells you a JSON-LD cross-reference went nowhere, which is why step four below is a thing you run yourself.

The rule is dull and it works. One script block per page, one @graph array inside it, every node given an explicit @id, every cross-reference resolving inside that array.

Valid, parsed, and still discarded

Three more ways to lose, none of which a syntax check can see.

Markup that describes invisible content. Google's structured data policies are explicit that you should not add structured data about information that is not visible to the user, even when the information is accurate. A FAQPage node whose questions appear nowhere on the rendered page is parsed, understood, and then ineligible under that policy. The data was never the problem. That is Google's published rule, and no other engine publishes one we can quote against it.

Markup that contradicts the page. If the node says datePublished 2024 and the dateline area of the page says updated last month, you have handed a retrieval system two facts and no way to choose. We cannot see how an engine resolves that conflict, and we have found no engine documentation that describes the tie-break, so the honest version of the advice is simply that agreeing with yourself costs nothing and removes the question.

Markup that arrives late. Google's introduction to structured data says it can read JSON-LD that is dynamically injected into a page, by JavaScript or by an embedded widget. That statement is about Google, which renders, and it does not carry over to retrievers that do not. The log study Vercel published with MERJ on 17 December 2024, covering crawler traffic across Vercel's network, found the OpenAI, Anthropic, Perplexity, Meta and Common Crawl agents downloading JavaScript assets with no sign of executing them, while Gemini's crawling ran on Google's rendering infrastructure. That is one network over one period, not the whole web, and we have found no engine documentation of its renderer to check it against. On that evidence, a block written into the DOM by a tag manager is readable to some of this audience and not the rest. If the schema matters, it belongs in the server response. The ordering question behind this is the same one covered in Structured data for AI search: what to mark up first.

Scale, so you know how exposed you are

The HTTP Archive Web Almanac, in its structured data chapter published on 11 November 2024, measured JSON-LD on 41 percent of mobile pages, up from 34 percent in 2022. The type breakdown is the part worth staring at: in that same crawl, WebSite appeared on 12.73 percent of mobile pages and Organization on 7.16 percent, while Article sat at 0.18 percent and Product at 0.77 percent. Those are counts of pages carrying a type, not a judgement of whether the markup was any good. Read plainly, they say that the typical marked-up page in that crawl carried a thin site-identity node and little else. A low bar is good news if you want separation. It also means one silent drop in your Organization node removes most of what you say about yourself.

A check that takes ten minutes

Run this against one real template, not a hand-built example.

  1. Fetch the page with curl and no JavaScript. If the ld+json block is missing from the
  2. raw HTML, stop here and fix that first.

  3. Pipe it through a JSON parser. jq empty will catch every one of the thirteen syntax
  4. errors in Google's list.

  5. Expand the document, then diff. Any property that was in your source and is absent from
  6. the expanded output failed to resolve and was dropped. This is the step that catches the typo, and it is the step no validator asks you to run.

  7. Collect every @id in the file. Check for duplicates, then check that every {"@id": ...}
  8. reference resolves to a node present in the same document.

  9. Compare each marked-up value against the rendered page with your own eyes.

Step three is the one that pays. A validator tells you whether the node is valid; expansion tells you what survived. The self-check has the rest of the non-schema version of this exercise, and the wider argument for why access and legibility come before content sits in what is answer engine optimisation.

Honest limits

This post establishes what a JSON-LD processor does with broken markup. It does not establish what any engine does with the result. We have found no published documentation from any engine that says how much weight it gives schema relative to visible text, and an absence of documentation is not evidence that the weight is high or low. The failure mechanisms here are read off the W3C specification and Google's own help pages, so they are safe for Google and are an inference, not a finding, for engines that publish no equivalent documentation. The crawler behaviour in the rendering section comes from one log study of one network in December 2024, and crawler fleets change. Our own sense that these mistakes are common comes from client work we have not measured in a way an outsider could reproduce, so we have put no number on it.

Naming conventions around all of this are a separate mess, covered in AEO vs GEO vs SEO: what the three terms actually mean.

FAQ

Does a misspelled property break the whole JSON-LD block? No, and that is the problem. Expansion drops the key it cannot resolve and keeps the rest, so the block still validates and the property is gone without a message.

Should everything go in one script tag or several? One, with a @graph array, unless you have a reason not to. Cross-references between nodes resolve reliably inside a single document, and multiple blocks make @id collisions and dangling references much easier to create by accident.

Is the Rich Results Test enough? It is a good start and it is scoped to Google's rich result requirements, so it checks required properties for supported types rather than general correctness. Pair it with a schema.org validator and with the expansion diff described above.

Does JSON-LD added by a tag manager count? Google's documentation says it can read JSON-LD injected into the page, and Gemini's crawling was observed on Google's rendering infrastructure in the Vercel and MERJ study of December 2024. The same study saw the OpenAI, Anthropic, Perplexity, Meta and Common Crawl agents fetch JavaScript without executing it. For those, markup that only exists after execution is not there. Server-rendered markup is the safer default when the markup matters.

Sources

Related reading