Beginner 6 min · March 29, 2026

JSON Trailing Comma — Invisible SyntaxError at Position 284

Trailing comma after last property gives SyntaxError at pos 284.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
Quick Answer
  • JSON has 6 value types: string (double quotes only), number, boolean (true/false), null, object, array. No Date, undefined, or function.
  • Object: { "key": "value" } — keys MUST be double-quoted. No trailing comma after last property.
  • Array: [1, 2, 3] — ordered list. Return [] for empty lists, not null (null crashes .forEach).
  • Production trap: JSON.stringify() drops undefined values silently — your keys disappear with no error.
  • Silent killer: price as "1999" (string) instead of 1999 (number) → "1999" + 499 = "1999499", not 2498. No error, just wrong math.
  • Biggest mistake: adding comments to JSON (// comment) — JSON has no comment syntax. Strips comments before parsing.

A single trailing comma in a JSON config file took down a fintech startup's entire deployment pipeline for four hours. Not a logic error. Not a race condition. One comma after the last property in an object — invisible to the eye, fatal to the parser. That's the world you're stepping into.

JSON — JavaScript Object Notation — is the lingua franca of the modern web. REST APIs send it. Configuration files are written in it. Databases like MongoDB store it. Mobile apps receive it. You cannot build anything networked in 2026 without touching JSON constantly. The problem isn't that it's complicated. It's that it looks deceptively simple, so people get sloppy, and sloppy JSON doesn't degrade gracefully — it throws a hard error and stops everything cold.

By the end of this, you'll be able to write valid JSON from scratch without second-guessing yourself, read a raw JSON payload and instantly spot what's wrong with it, understand exactly why each syntax rule exists, and debug the specific parser errors that make junior devs spend an hour staring at code that looks perfectly fine.

What JSON Actually Is — and Why the Rules Are So Unforgiving

Before JSON existed, developers exchanged data using XML. It looked like HTML — verbose, nested tags everywhere, an absolute nightmare to parse and read. A simple user record might take 20 lines of XML. The same data in JSON takes 5. JSON was designed by Douglas Crockford in the early 2000s as a minimal, human-readable data format that any language could parse with a trivial amount of code.

The strictness isn't arbitrary bureaucracy. JSON is designed to be parsed by machines across every programming language on the planet. If the format allowed ambiguity — JavaScript-style trailing commas, single quotes, unquoted keys — every parser would need to handle edge cases differently. Two services would argue about what a payload means. Data corruption follows. The strict rules are what make universal interoperability possible.

JSON only knows six value types: strings, numbers, booleans (true/false), null, objects, and arrays. That's it. No dates. No functions. No undefined. No comments. If you're trying to put a JavaScript Date object directly into JSON, you're going to have a bad time — and we'll cover that. First, understand the two structural building blocks everything else is built from: objects and arrays.

A key insight: JSON is a data interchange format, not a programming language. It's meant to be read by machines, not written by hand. The strictness is a feature, not a bug. Every time you manually edit a JSON file, you're at risk of introducing syntax errors. Use tools (linters, formatters, schema validators) to help you.

JSON Objects: The Key-Value Store That Powers Every API Response

A JSON object is a collection of key-value pairs wrapped in curly braces. The key is always a double-quoted string. The value is any of the six legal JSON types. Key and value are separated by a colon. Pairs are separated by commas. The last pair gets no trailing comma — this is the rule that bites people constantly.

Why no trailing comma? Because JSON was designed to be a strict subset of a specific version of JavaScript from 2001. Trailing commas weren't valid JavaScript then. The spec was frozen, and that decision became permanent. Every JSON parser in every language since then has inherited this constraint. Like it or not, that's the deal.

Objects can nest inside objects. A user object can contain an address object. That address object can contain a geo object. There's no technical depth limit — but if you're nesting more than three or four levels deep in a production API response, that's a design smell. You're probably shipping more structure than the client needs, which wastes bandwidth and makes the payload harder to consume.

null vs missing key: There's a meaningful difference between "discountCode": null and not including the discountCode key at all. null means "this field exists and has no value" — the API is explicitly telling you that no discount was applied. A missing key means "we have no information about this field" — maybe the discount system didn't respond, or maybe the field is optional. Pick one convention per field and document it. Mixing them silently breaks consumers who check if (response.discountCode) expecting a falsy value.

JSON Arrays: Ordered Lists and the Gotchas Hidden Inside Them

A JSON array is an ordered, comma-separated list of values wrapped in square brackets. The values don't have to be the same type — an array can hold strings, numbers, objects, other arrays, nulls, whatever you want. In practice, mixing types in a production array is a terrible idea because every consumer has to handle it defensively, but the spec allows it.

Arrays are zero-indexed. The first item is at index 0. This matters when you're debugging a parser error and the error message tells you the problem is 'at position 0 in the array' — it means the first element.

Where arrays get genuinely tricky in production is arrays of objects. This is the pattern behind almost every list endpoint in any REST API you'll ever call. A GET /products endpoint returns an array of product objects. A GET /orders endpoint returns an array of order objects. Each object in the array must individually follow all JSON object rules — double-quoted keys, no trailing commas, no comments. I've seen a team waste a full sprint debugging an import feature because a third-party vendor was sending an array where one object out of 500 had a trailing comma. The other 499 parsed fine. That one object silently corrupted the batch.

Empty array vs null: [] is an empty array. null is a null value. If your API returns "products": null when there are no results, every consumer has to add a null check before calling .forEach() or .map(). Someone will forget, causing TypeError: Cannot read properties of null (reading 'forEach') in production. Always return [] for empty lists. Always.

The Exact JSON Errors That Break Production Code — and How to Fix Them

JSON errors aren't subtle. The parser hits something invalid and throws immediately with a SyntaxError. The problem is the error message tells you where in the string the parser gave up — not where the actual mistake is. If your JSON is 800 lines long and the error says 'position 4721', good luck finding it without knowing what to look for.

Here are the six mistakes I've personally seen break production systems. Not hypothetical mistakes — real incidents, real error messages, real fixes. These aren't sorted by frequency. They're sorted by how long it takes a junior developer to spot them without knowing they exist.

The nastiest one isn't a syntax error at all. It's type coercion — sending a price as the string '1999' instead of the number 1999. The JSON is perfectly valid, it parses without error, and then your checkout service calculates a total of '1999' + '499' = '1999499' instead of 2498. That specific bug caused a real e-commerce platform to charge customers the wrong amount for six hours before anyone noticed. No error. No alert. Just wrong numbers.

The solution is schema validation. Tools like Zod (TypeScript), Ajv (JavaScript), or Pydantic (Python) validate the shape and types of JSON at the boundary before your business logic touches it. A simple z.object({ price: z.number() }) would have caught the string price immediately. Don't parse JSON and trust it. Validate it.

JSON Object vs JSON Array
AspectJSON Object {}JSON Array []
StructureKey-value pairs — each value has a nameOrdered list — values accessed by numeric index
Key requirementKeys must be double-quoted stringsNo keys — position is the identifier
Order guaranteeOrder not guaranteed by spec (though most parsers preserve it)Order is guaranteed and meaningful
Access patternparsed.customer.emailparsed.products[0].name
Best forA single entity with named properties (one user, one order)Multiple entities of the same type (list of users, list of orders)
Empty state{} — empty object, zero properties[] — empty array, zero elements
Nested inside each otherObjects can contain arrays as property valuesArrays can contain objects as elements
Type mixing allowedEach property can have a different typeElements can be different types — but don't do it in production

Key Takeaways

  • Trailing commas are invisible, fatal, and point to the wrong line in error messages. Lint them automatically with jq empty in CI.
  • Single quotes and unquoted keys are valid in JavaScript but invalid in JSON. Use double quotes everywhere. No exceptions.
  • undefined values in objects are silently dropped by JSON.stringify(). Your keys vanish with no error. Replace with null if you need the field to exist.
  • Return [] for empty lists, not null. null crashes .forEach() and .map(); [] doesn't.
  • A price as a string "1999" instead of a number 1999 is valid JSON but corrupts arithmetic. Validate schemas at API boundaries with Zod or Ajv.
  • JSON has no comment syntax. Comments cause parse failures. Use a separate documentation file or JSON Schema's $comment field.
  • The safe round-trip subset is JSON's six types: string, number, boolean, null, object, array. Everything else transforms or disappears.

Common Mistakes to Avoid

  • Adding a trailing comma after the last property in an object or the last element in an array
    Symptom: SyntaxError: Unexpected token } in JSON at position N. The error message points to the closing brace, not the comma itself — making it hard to find.
    Fix: Remove the trailing comma. Use a JSON linter (jq empty file.json) in CI to catch this automatically. Configure your editor to show trailing commas as errors.
  • Using single quotes for keys or string values instead of double quotes
    Symptom: SyntaxError: Unexpected token ' in JSON at position 1. The parser chokes on the very first character.
    Fix: Replace all single quotes with double quotes for keys and string delimiters. Single quotes inside strings (e.g., "O'Malley") are fine — they're just characters, not delimiters. Use a JSON linter or a simple regex find-replace: sed "s/'/\"/g" file.json — but be careful not to escape single quotes inside strings.
  • Leaving an unquoted key in an object
    Symptom: SyntaxError: Unexpected token n in JSON at position 1. The error message shows the first character of the unquoted key.
    Fix: Wrap all keys in double quotes. Valid: {"name": "Alice"}. Invalid: {name: "Alice"}. Many developers copy JavaScript object literals directly into JSON files — this is guaranteed to break production.
  • Putting comments inside JSON (// or /* */)
    Symptom: SyntaxError: Unexpected token / in JSON at position N. JSON has no comment syntax — the parser sees the slash and throws.
    Fix: Remove all comments. If you need to document a JSON file, use JSON Schema with the $comment keyword (allowed by the spec specifically for this purpose), or keep documentation in a separate markdown file. Never put // or /* in production JSON — it will cause a hard parse failure.
  • Passing objects with undefined values through JSON.stringify() without handling them
    Symptom: No error. The serialised JSON is missing keys that you expected to be present. Silent data loss.
    Fix: Replace undefined with null before stringification using a replacer function: JSON.stringify(obj, (key, val) => val === undefined ? null : val). Or use null instead of undefined in the original object—null is preserved in JSON, undefined is not.
  • Storing numbers as strings in JSON (e.g., price as "1999" instead of 1999)
    Symptom: No parse error. The API appears to work. But arithmetic operations produce incorrect results: `"1999" + 499 = "1999499"` instead of `2498`. This is especially dangerous in financial systems.
    Fix: Enforce schema validation with Zod or Ajv at the API boundary. Define that price fields must be numbers: z.object({ price: z.number() }). Never trust that a field from an external API is the right type — add explicit coercion: const price = Number(parsed.price).
  • Returning null for empty arrays instead of []
    Symptom: Client code that expects `.forEach()` or `.map()` on the array crashes with `TypeError: Cannot read properties of null (reading 'forEach')`. Production outage at exactly the moment the list becomes empty.
    Fix: Always return [] for empty lists. If you must return null for legacy reasons, document it clearly and add client-side fallback: const items = response.items || []. Better yet, fix the API to return [].

Interview Questions on This Topic

  • QWhat's the difference between JSON.stringify() and JSON.parse() in terms of data loss? Walk me through the exact set of JavaScript values that survive a round-trip (stringify then parse) unchanged, which ones change type, and which ones disappear entirely.SeniorReveal
    JSON.stringify() converts a JavaScript value to a JSON string. JSON.parse() reverses the process. But not all values survive intact: Survive unchanged: strings, numbers, booleans (true/false), null, arrays of these, objects with string keys and these values. Change type: - Date objects become ISO 8601 strings ("2024-01-01T00:00:00.000Z"). They don't become Date objects on parse — they stay strings. You must manually new Date(parsed.isoString). - NaN, Infinity, -Infinity become null when stringified. Disappear entirely (silent data loss): - undefined values — the entire key-value pair is omitted, not included as null. - Function objects — omitted entirely. - Symbol values — omitted entirely. Throw an error (no output): - Circular references — TypeError: Converting circular structure to JSON. - BigInt values — TypeError: Do not know how to serialize a BigInt (you can add a toJSON method to handle them). The safe round-trip subset is JSON's six data types: string, number, boolean, null, object (with string keys), array. Anything else either changes type, disappears, or throws an error.
  • QYou're receiving a JSON webhook from a third-party vendor that sometimes sends price as a string ("19.99") and sometimes as a number (19.99). You can't change the vendor. How do you defend your service against this type inconsistency without polluting your business logic with type checks everywhere?SeniorReveal
    Defend at the boundary using a schema validation library with type coercion: Option 1 — Zod with transform: ``typescript const WebhookSchema = z.object({ price: z.union([z.number(), z.string()]).transform(val => Number(val)) }); const validated = WebhookSchema.parse(rawPayload); // validated.price is guaranteed to be a number ` Option 2 — Ajv with custom keyword: Use a JSON Schema with type: "number"` and a pre-processing step that coerces string numbers before validation. Option 3 — Custom middleware: In an Express/Node.js app, add a middleware that recursively traverses the parsed object, converts any string that looks like a number to a number, and logs a warning when it does so. Why this is better than scattering type checks in business logic: (1) You have one source of truth for type expectations, (2) failing fast at the boundary prevents corrupted data from propagating, (3) you can alert when type mismatches happen and work with the vendor to fix their API.
  • QYour team maintains a large JSON configuration file used by multiple services across the company. A junior developer adds a trailing comma after the last property, and your deployment pipeline fails with a cryptic Unexpected token } error. How do you prevent this from happening again at the organisational level?SeniorReveal
    Multi-layered defence: Layer 1 — Local developer tooling: Configure VS Code (or team's standard editor) to mark trailing commas as errors: "json.trailingCommas": "error" in settings.json. Add a pre-commit hook using husky + lint-staged that runs jq empty on all JSON files before commit. Layer 2 — CI automation: Add a validate-json step to the CI pipeline that runs jq empty --exit-status config.json on every JSON file. Use jq . config.json > /dev/null to validate while also normalising (but jq empty is faster for validation-only). Layer 3 — Schema validation: Use JSON Schema with a linter like ajv-cli to validate both syntax and structure. A schema also catches type errors, missing required fields, and extra properties. Layer 4 — Apply the same validation to all environments: Ensure that dev, staging, and production use the same strict JSON parser. No tolerance for trailing commas anywhere. The cultural lesson: This isn't a technical problem — it's a training problem. Add this to the onboarding checklist. Put a note in the team runbook. Make it part of the definition of 'done' for any change touching a JSON file.

Frequently Asked Questions

Why does JSON.parse keep throwing SyntaxError even though my JSON looks correct?

The most likely culprit is a trailing comma after the last property in an object or array — JSON.parse('{"name": "Alice",}') throws even though JavaScript itself allows trailing commas in object literals. Paste your JSON into jsonlint.com, which points to the exact line and character of the violation. The second most common cause is single quotes — JSON requires double quotes everywhere, no exceptions.

What's the difference between a JSON object and a JSON array?

An object is a named collection — you access values by their key, like parsed.username. An array is an ordered list — you access values by their position, like parsed.items[0]. Use an object when you're describing a single thing with properties. Use an array when you have multiple things of the same kind.

How do I handle a JavaScript Date object in JSON since JSON doesn't have a date type?

JSON.stringify() automatically converts Date objects to ISO 8601 strings like '2024-03-15T09:30:00.000Z'. JSON.parse() brings that back as a plain string, not a Date object. You have to explicitly reconstruct it: const createdAt = new Date(parsed.createdAt). Document in your API contract that all timestamps are ISO 8601 UTC strings and parse them at the boundary — never deeper in your business logic.

Can JSON handle circular references, and what happens in production if an object with one gets serialised?

JSON.stringify() throws TypeError: Converting circular structure to JSON the moment it encounters a circular reference. The entire serialisation fails — no partial output, no graceful fallback, just a hard error. This has burned teams who pass Express request or response objects into a logger that calls JSON.stringify() internally. The fix is either to sanitise the object before logging using a library like flatted (which serialises cycles using a path reference scheme) or safe-json-stringify, or to use structured logging libraries like Pino that handle circular references internally.

What's the difference between `null` and omitting a key entirely in a JSON API response?

"discountCode": null means 'this field exists and has no value' — the API is explicitly telling you that no discount was applied. A missing key means 'we have no information about this field' — maybe the discount system didn't respond, or maybe the field is optional. Mixing the two conventions in the same API is a design flaw. Pick one per field and document it. When consuming an API, check for both: if (response.hasOwnProperty('discountCode') && response.discountCode !== null).

🔥

That's JS Basics. Mark it forged?

6 min read · try the examples if you haven't

Previous
Object Methods in JavaScript
16 / 16 · JS Basics
Next
Closures in JavaScript