/ โ† JSON-LD Fixes / JSON Syntax Errors
In this guide
  1. What one bad character does
  2. Parser messages and what they really mean
  3. Pinpointing the fault in a few minutes
  4. Where broken JSON comes from
  5. Encoding values properly, by language
  6. The fix, step by step
  7. Cases that catch people out
  8. Catching it before it ships
  9. Questions and answers

What one bad character does

A browser treats a <script type="application/ld+json"> element as inert data. It never runs it and never complains about it, which is why a broken block can sit on a live site for months. The consumers that do read it, such as Googlebot, Bing, social platforms and AI crawlers, pass the text to a JSON parser. JSON parsers are all-or-nothing: they either return a complete object or they return an error and nothing else.

The practical effect is wider than people expect:

Because the failure is silent, syntax problems are usually found by accident, after a drop in rich results or a report from an audit tool. The JSON Checker parses every block on the page and reports the syntax first, because nothing else in a block can be checked until it reads cleanly.

Parser messages and what they really mean

Different parsers word the same fault differently. These are the messages you are most likely to meet, with the usual cause behind each.

Message (typical wording)Usual causeWhat to look at
Expected double-quoted property name / Expecting property name enclosed in double quotesA comma after the last property, or a key written in single quotes or with no quotes.The character just before the reported position, usually a , followed by }.
Expected ',' or '}' after property value / Expecting ',' delimiterA double quote inside a value ended the string early, or a comma is missing between two properties.The last string value before the position: look for " characters inside text such as measurements or quoted phrases.
Bad control character in string literal / Invalid control characterA raw line break or tab inside a string, usually from a multi-line description field.Descriptions, addresses and review bodies pulled from text areas.
Unexpected token / Unexpected characterCurly quotes, a comment, a lone undefined or NaN, or a template tag that was not processed.Non-ASCII quote marks and leftover template syntax such as {{ }} or <?php.
Unexpected end of JSON input / Unterminated stringThe block was cut short: a closing bracket is missing, or the content contains </script> which ended the element early.The end of the block and any HTML stored in string values.
Extra data / Unexpected non-whitespace character after JSONTwo objects written one after the other with no array around them, or content after the final brace.Blocks built by joining several snippets together.
Invalid \escape / Bad escaped characterA backslash followed by a character JSON does not allow, often from Windows paths or regular expressions pasted into text.Any \ in a value that is not followed by ", \, /, b, f, n, r, t or u.
๐Ÿ’ก Positions are counted from the start of the text you gave the parser, not from the top of the page. Paste the block on its own, from the first { or [, so the numbers line up with what you are looking at.

Pinpointing the fault in a few minutes

Look at the served HTML, not the Elements panel

The Elements panel in developer tools shows the page after scripts have run and after the browser has tidied the markup. For JSON-LD you need the bytes the server actually sent, which is what a crawler receives first. Use view source (Ctrl+U, or Cmd+Option+U on a Mac), or fetch the page from the command line with curl -s https://www.example.co.uk/page/ > page.html. If a tag manager or JavaScript adds the block later, check the rendered version too, since that is a separate source of errors.

Parse every block in one go from the console

Open the page, open the console, and paste this. It tries each block and prints the error for any that fail, together with the text around the failure point.

document.querySelectorAll('script[type="application/ld+json"]').forEach((el, i) => {
  const text = el.textContent;
  try { JSON.parse(text); console.log('Block ' + i + ': OK'); }
  catch (e) {
    const m = /position (\d+)/.exec(e.message);
    const p = m ? Number(m[1]) : 0;
    console.log('Block ' + i + ': ' + e.message);
    console.log(text.slice(Math.max(0, p - 80), p + 20));
  }
});

This checks the rendered DOM, so it catches blocks that scripts inject, but it will not show HTML entities exactly as the server sent them. If the console reports every block as fine and a crawler still says the markup is unparsable, compare against view source.

Parse a saved block on the command line

Save the block's contents, without the script tags, to a file and run python3 -m json.tool block.json or jq . block.json. Python reports a line and column, which is easier to follow in an editor than a character position. Turn on "show invisible characters" or "render whitespace" in your editor as well: non-breaking spaces, zero-width characters and byte order marks are invisible but matter.

Where broken JSON comes from

Almost nobody types invalid JSON by hand into a finished page. It is produced by one of a handful of processes, and finding which one tells you where the lasting fix belongs.

Encoding values properly, by language

The durable fix is the same everywhere: build the data as a native object or array, then turn the whole thing into JSON with the language's own encoder. Encoders escape quotes, backslashes and control characters, never leave trailing commas and write numbers correctly.

EnvironmentUseNote
PHPjson_encode($data, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG)JSON_HEX_TAG turns < and > into \u003C and \u003E, so stored HTML cannot close the script element.
WordPresswp_json_encode( $data )Handles character set problems that plain json_encode can fail on. Hook it into wp_head rather than pasting raw JSON into a theme file.
Shopify Liquid{{ product.title | json }}Outputs the value with its surrounding quotes, so do not add your own.
JavaScript / NodeJSON.stringify(data).replace(/</g, '\u003c')The replace step protects against </script> inside values when you write the result into HTML.
Pythonjson.dumps(data, ensure_ascii=False)Then replace < with \u003c before placing it in a template, and mark the output as safe so the template engine does not HTML-escape it.
Twig{{ data|json_encode|raw }}Without raw, Twig escapes the quotes into entities and the block breaks.

Before and after

This fragment was built by joining strings. The inch marks in the product name close the string early and the loop left a comma after the final property:

{
  "@type": "Product",
  "name": "Shelf bracket 6" black",
  "sku": "SB-6-BLK",
}

The same data passed through an encoder produces a block that parses:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Shelf bracket 6\" black",
  "sku": "SB-6-BLK",
  "description": "Powder-coated steel bracket.\nSupports shelves up to 20 cm deep."
}
</script>

Notice the escaped quote after the 6 and the \n in place of a real line break. You would never type these by hand; the encoder writes them for you.

โš ๏ธ Do not run the finished JSON through an HTML escaping function. Inside a script element the browser does not decode entities, so &quot; reaches the parser as five literal characters and breaks the block.

The fix, step by step

Step 1

Pull the exact block the server sends

Open view source on the affected page, or save it with curl, and copy each application/ld+json block from its first bracket to its last. Do not copy from the Elements panel, because the browser may have reformatted it. Note which page template the URL uses and whether the block sits in the head or body, because that position often hints at which theme file, plugin or app printed it.

Step 2

Run it through a strict parser

Run the page through the JSON Checker, then paste the failing block into python3 -m json.tool, jq or the browser console with JSON.parse. Use a strict parser rather than a lenient online formatter, because some formatters silently repair trailing commas and single quotes and then tell you the JSON is fine. Keep the exact error message and the reported line, column or character position for the next step.

Step 3

Read the error position backwards

Go to the reported position and then read to the left. A parser only notices a problem when it meets a character it cannot accept, so the real mistake is usually earlier: an unescaped quote in the previous value, a missing comma at the end of the previous line, or a bracket that was never closed. Turn on invisible characters in your editor to expose curly quotes, raw tabs and non-breaking spaces.

Step 4

Trace the bad character to its source

Decide where the faulty text came from: a product or post field, a theme template, an SEO plugin, an app, a tag manager or a custom HTML block. Search your theme files for ld+json, disable plugins one at a time on a staging copy, or compare the block with the admin field it came from. Fixing the single page by hand helps nobody if the same template breaks the next page tomorrow.

Step 5

Replace string building with a JSON encoder

Rewrite the template so it builds an object or array and prints it once with json_encode, wp_json_encode, the Liquid json filter or JSON.stringify. Leave out properties whose values are empty instead of printing blank numbers. If the block comes from a plugin or app you cannot edit, clean the source field instead, report the fault to the vendor, and switch that output off if it keeps breaking.

Step 6

Add a parse check before every deploy

Add a small script to your build or deployment that extracts every application/ld+json block from the generated pages and parses it, failing the build on any error. Then re-run the JSON Checker on one page of each template, and in Search Console use Validate fix on the unparsable structured data report so Google rechecks the affected URLs as they are recrawled.

Cases that catch people out

Catching it before it ships

A parse check is cheap and catches almost all of these faults before a visitor or crawler sees them. This Python script reads every HTML file in a build folder and fails if any JSON-LD block does not parse:

import json, pathlib, re, sys
bad = 0
pattern = re.compile(r'<script[^>]*application/ld\+json[^>]*>(.*?)</script>', re.S | re.I)
for page in pathlib.Path('public').rglob('*.html'):
    for n, block in enumerate(pattern.findall(page.read_text(encoding='utf-8'))):
        try:
            json.loads(block)
        except json.JSONDecodeError as e:
            bad += 1
            print(f'{page} block {n}: {e}')
sys.exit(1 if bad else 0)

On a CMS without a build step, run the same check against a list of live URLs each week, one per template, and after every theme, plugin or app update. Train editors not to paste formatted text into fields that feed structured data, and prefer plain text fields for product names, headlines and summaries. For what to fix once every block parses, the missing required fields guide is the natural next stop.

Questions and answers

1. Why does my JSON-LD work in one validator and fail in another?

Some online tools are deliberately lenient. They repair trailing commas, accept single quotes or strip comments before parsing, then report success, while search engines use strict parsers that reject the same text. Other differences come from what the tool fetched: one may read the raw HTML the server sent, another the rendered page after scripts ran. Always test with a strict parser and compare view source with the rendered DOM when results disagree.

2. Does a syntax error in one block break all the structured data on the page?

No, only the script element that contains the error. Each JSON-LD block is parsed on its own, so a broken block from a review app does not remove the valid block your theme prints. The catch is that many sites put everything into one block with a graph, in which case a single bad character removes every entity at once. That is a good reason to fix syntax errors before anything else on the report.

3. Can JSON-LD contain comments?

No. JSON has no comment syntax at all, so double slashes, slash-star blocks and HTML comments inside the script element all cause parse errors. If you need to leave notes for other developers, put them in the template language outside the output, for example a Liquid comment tag or a PHP comment, so they never reach the page. Some documentation examples include comments for explanation, so remove them before pasting any sample into a site.

4. How do I include a double quote inside a JSON-LD value?

Escape it with a backslash, so a six inch shelf becomes 6 followed by backslash and quote, or reword the value to avoid the mark, for example 6-inch. Doing this by hand is error-prone, so the better answer is to let an encoder do it: json_encode in PHP, the json filter in Liquid or JSON.stringify in JavaScript all escape quotes automatically. Never replace the quote with an HTML entity, because entities are not decoded inside script elements.

5. Why does Search Console say unparsable structured data when my page looks fine?

Visitors never see JSON-LD, so a broken block has no visible effect on the page. Search Console reads the code, not the design, and reports any block its parser rejected. Open the report, pick an example URL and test it live with URL Inspection or the Rich Results Test to see the message. Then parse the served HTML yourself to find the exact character, because the report rarely names the template that caused it.

6. Is it safe to fix the JSON directly in the page HTML?

Only as a stopgap. If the block is printed by a template, a plugin or an app, your manual edit is overwritten the next time the page is saved, the cache is cleared or the software updates, and every other page using the same template remains broken. Find the template or setting that generates the block and fix it there. Hand edits make sense only for genuinely hand-written blocks on one-off pages such as a contact page.

7. Do HTML minifiers or caching plugins break JSON-LD?

Most handle it correctly, but some optimisation tools treat every inline script as JavaScript, then combine, defer or rewrite it. That can strip line breaks inside strings, change the type attribute or move the block so it no longer parses as intended. If the block is valid in your template output but invalid on the live page, exclude application/ld+json scripts from minification or deferral in the tool settings and test again after clearing the cache.

Find the block that will not parse

The JSON Checker parses every JSON-LD block on your page, validates the syntax, checks each type against schema.org, verifies required fields, detects conflicting types and runs the result against Google rich result criteria.

Open JSON-LD Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All JSON-LD Fixes  ·  JSON-LD Schema Guide  ·  Missing Required Fields  ·  Multiple Competing Schemas  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.