LW IT Solutions
« Blog Overview /Web Development/Tutorials / Tutorial: Checking a JSON Payload Before It...
Read this article in other languages:

Tutorial: Checking a JSON Payload Before It Goes Into a Tag

Tutorial: Checking a JSON Payload Before It Goes Into a Tag
Contents
  1. Why a JavaScript Object Is Not JSON
  2. The Five Errors That Raise an Exception
  3. The Two That Report Nothing
  4. Reading the Position a Parser Reports
  5. Checking It on the Command Line
  6. Where It Matters Most
  7. Questions and answers
  8. Sources

A payload that a parser rejects is annoying and harmless: something breaks immediately, in one place, and the fix takes a minute. A payload that a parser accepts and reads differently than intended is neither of those things.

Both kinds come from the same source. JSON looks like a JavaScript object and is a much narrower format, and the differences between them are exactly the list below.

A payload with seven numbered problems marked inline, and beneath it two columns separating the five that raise an exception from the two that change a value silently
Five of the seven stop the parser. The other two produce a document that parses cleanly and means something else.

Why a JavaScript Object Is Not JSON

Everything valid in JSON is valid JavaScript. The reverse does not hold, and the gap between them is where the errors live.

{                                     // JavaScript, not JSON
  'order_id': 9007199254740993,       // single quotes
  total: 129.90,                      // unquoted key
  items: [ { sku: "SKU-42", qty: 2, } ],   // trailing comma
  rabatt: NaN,                        // not a JSON value
  /* a comment */
}

JSON permits double quotes and nothing else, requires every key to be quoted, forbids a comma before a closing bracket, has no comments, and knows exactly seven value types: object, array, string, number, true, false and null. There is no undefined, no NaN, no Infinity and no date.

That last point is worth stating plainly, because it produces a recurring surprise: a date has no representation of its own. Whatever arrives is a string or a number that both sides have agreed to read as a date, and any disagreement about the format is invisible to the parser.

The Five Errors That Raise an Exception

These are the loud ones, and every one of them produces the same unhelpful message about an unexpected token.

Error What it looks like
Single quotes ‘order_id’ instead of “order_id”
Unquoted key total: 129.90 instead of “total”: 129.90
Trailing comma A comma before } or ]
Comment Anything after // or between /* and */
Raw line break in a string A newline inside quotes; it has to be \n

A sixth belongs in the same group without being visible at all: a byte order mark at the beginning of the file. It is three invisible bytes before the opening brace, and the parser reports an unexpected token at position 0 – which looks like the brace is the problem. A file that looks flawless and fails at position 0 has one, and an editor set to save without a BOM removes it.

The Two That Report Nothing

These are the ones worth hunting for on purpose, because nothing else will point at them.

The first is a duplicate key. The specification allows it, and the common behaviour is that the last one wins – so a document with "total" twice parses cleanly and carries the second value. Where the two are far apart in a long payload, the result is a number that is neither wrong-looking nor right.

{ "total": 129.90, "currency": "EUR", "total": 139.90 }

JSON.parse(...).total   →   139.90

The second is a large integer. Numbers in JavaScript are floating point, and integers stay exact only up to 2^53 − 1. An order number, an ID from a database, a Meta CAPI identifier – all of them regularly exceed that, and the parser rounds them without a word.

JSON.parse('{"id": 9007199254740993}').id   →   9007199254740992

JSON.parse('{"id": "9007199254740993"}').id →   "9007199254740993"

The rule that follows is short: an identifier is a string, always, even when it consists only of digits. Nothing is ever calculated with it, and a string survives every parser unchanged.

Reading the Position a Parser Reports

The position in the error message is a character offset into the whole document, which in a minified payload is a number without meaning.

The first step is therefore always to format the document, not to look for the error. A formatted payload puts the position onto a line, and the line above the reported one is usually where the fault actually is – a missing comma is noticed at the next token, not at the place it is missing from.

try {
  JSON.parse(text);
} catch (e) {
  const stelle = Number((e.message.match(/position (\d+)/) || [])[1]);
  if (!Number.isNaN(stelle)) {
    console.log(text.slice(Math.max(0, stelle - 60), stelle + 60));
    console.log(" ".repeat(Math.min(60, stelle)) + "^");
  }
}

Sixty characters either side of the position is enough to see the problem in almost every case, and the caret removes the counting. This is worth having as a snippet, because the alternative is opening a formatter, pasting, scrolling and losing the position.

Checking It on the Command Line

Where the payload comes out of a file or a request, a single command answers the question.

# valid? formatted output, or an error with a line number
jq . nutzlast.json

# only the answer, without the content
jq -e . nutzlast.json >/dev/null && echo "gueltig" || echo "ungueltig"

# find duplicate keys - --stream reports every pair, including the repeated one
jq -c --stream 'select(length == 2) | .[0]' nutzlast.json | sort | uniq -d

# large numbers that will lose precision
grep -oE '"[a-z_]+": *[0-9]{16,}' nutzlast.json

The third command is the one worth running on a payload that has never been checked. It lists the paths that occur more than once, which is a mechanical way to find the silent case – and on a generated payload it finds something surprisingly often, because a template that appends a field in two branches produces exactly this.

Where It Matters Most

Three places turn a small JSON mistake into something that is hard to notice afterwards.

A JSON-LD block in the page is the first. A syntax error means search engines read no structured data at all, and the page keeps working perfectly – so nothing about the site suggests a problem, and the loss appears weeks later as missing rich results. A validator on the rendered page, not on the template, is the check that catches it.

A conversions API payload is the second. The receiving platform answers with an error, but that error is on a server-to-server response nobody is watching, and a failed event simply does not appear. This is the case where the duplicate key does the most damage: a payload with an event value twice sends the wrong one, and both the sender and the receiver consider the request successful.

A tag manager template configuration is the third. Its JSON is read once when the template is loaded, and a malformed field can leave the template installed but inert. What makes this one different is that it fails at the moment of import rather than at runtime – which is a better place to fail, and only if somebody reads the message.

What all three have in common: they are checked at the point where the payload is produced, not where it is consumed. A build step that runs the parser over every generated file costs nothing and catches all five loud errors before they leave the machine.

Questions and answers

With a duplicate key, does the last value win in every parser?

No, and that is where the greater danger lies. The JSON specification, RFC 8259, only says that names within an object should be unique, and it states explicitly that receivers behave differently when they are not: many keep the last value, others report an error, and some pass on all the pairs. JSON.parse, Python’s json module and jq keep the last one.

This becomes a problem as soon as two places read the same payload with different parsers. If a validator checks the first value and the receiver processes the last, the check has seen a different value from the receiver, and both sides report success.

Code that parses the payload itself can reject the case outright. In Python an object_pairs_hook that counts the keys of each object and raises an exception on a duplicate is enough; the silent case then becomes as loud as the five in the table.

Does jq . show a large number exactly as it appears in the file?

That depends on the version. Up to jq 1.6 every number is read as a floating-point value, and the formatted output shows 9007199254740993 as 9007199254740992, just like JSON.parse, so a saved copy of that output has lost the digit; only since jq 1.7 does a number from the file stay unchanged as long as no arithmetic is done on it. The reliable check is therefore the grep command from the article, because it reads the text of the file rather than the parsed value.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in Touch

Briefly describe your project or inquiry for a tailored response. This site is protected by reCAPTCHA.

Write a comment

Edge cases the article misses and questions about the code are welcome here.

The email address is not published. Required fields are marked with an asterisk.

Articles & categories

CCTV

Follow this category by RSS

Cloud & AI

All 17 articles in this category Follow this category by RSS

Data Privacy

All 19 articles in this category Follow this category by RSS

Digital Analytics

All 58 articles in this category Follow this category by RSS

Digital Marketing

All 37 articles in this category Follow this category by RSS

IT & Networks

All 19 articles in this category Follow this category by RSS

Music Production

All 17 articles in this category Follow this category by RSS

Raspberry PI

Follow this category by RSS

SaaS & Internet Earning

Follow this category by RSS

Smart Home

All 18 articles in this category Follow this category by RSS

Web Development

All 11 articles in this category Follow this category by RSS

WordPress Plugins & Tricks

All 14 articles in this category Follow this category by RSS