Homeβ€Ί JavaScriptβ€Ί JSON Syntax Explained: Objects, Arrays, and the Mistakes That Break APIs

JSON Syntax Explained: Objects, Arrays, and the Mistakes That Break APIs

Where developers are forged. Β· Structured learning Β· Free forever.
πŸ“ Part of: JS Basics β†’ Topic 16 of 16
JSON syntax errors silently break APIs and crash parsers.
πŸ§‘β€πŸ’» Beginner-friendly β€” no prior JavaScript experience needed
In this tutorial, you'll learn:
  • The no-trailing-comma rule is permanent and non-negotiable β€” memorise it, lint for it, and save yourself hours of debugging parser errors that point to the closing brace instead of the actual offending comma.
  • JSON.stringify() silently drops undefined values and functions without throwing β€” your object shape and your JSON shape can diverge without any error, which is why you validate your serialised output in tests, not just your source objects.
  • Return [] not null for empty lists β€” every API that returns null for an empty collection forces every consumer to add a defensive null check, and someone will always forget, causing a TypeError in production.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
⚑ Quick Answer
Think of JSON like a standardised packing slip that every warehouse in the world agrees to read. It doesn't matter if the sender is a Python server in Berlin or a JavaScript app in Tokyo β€” as long as the packing slip follows the exact same format, both sides can unpack it without confusion. Objects are the labelled compartments on that slip ('quantity: 3, item: shoes'), and arrays are the numbered rows when you have multiple items. The format is ruthlessly strict β€” one missing comma or one wrong type of quote and the entire slip is rejected at the door.

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 2024 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.

JsonValueTypes.js Β· JAVASCRIPT
1234567891011121314151617181920212223242526272829303132333435363738
// io.thecodeforge β€” JavaScript tutorial

// The six legal JSON value types β€” know these cold.
// Every valid JSON document is built from exactly these primitives.

const validJsonValues = {
  // 1. String β€” MUST use double quotes. Single quotes = invalid JSON.
  productName: "Running Shoe",

  // 2. Number β€” integer or decimal, no quotes around it.
  priceInCents: 9999,
  weightKg: 0.85,

  // 3. Boolean β€” lowercase only. True/False with capitals = invalid JSON.
  inStock: true,
  onSale: false,

  // 4. Null β€” lowercase only. Represents intentional absence of a value.
  discountCode: null,

  // 5. Object β€” a nested collection of key-value pairs (covered next section).
  dimensions: {
    lengthCm: 30,
    widthCm: 12
  },

  // 6. Array β€” an ordered list of values (covered after objects).
  availableSizes: [7, 8, 9, 10, 11]
};

// Convert a JavaScript object to a JSON string β€” this is what gets sent over the wire.
const jsonString = JSON.stringify(validJsonValues, null, 2);
console.log(jsonString);

// Parse a JSON string back into a JavaScript object β€” this is what your API receives.
const parsedBack = JSON.parse(jsonString);
console.log(parsedBack.productName); // "Running Shoe"
console.log(typeof parsedBack.inStock); // "boolean" β€” not a string
β–Ά Output
{
"productName": "Running Shoe",
"priceInCents": 9999,
"weightKg": 0.85,
"inStock": true,
"onSale": false,
"discountCode": null,
"dimensions": {
"lengthCm": 30,
"widthCm": 12
},
"availableSizes": [
7,
8,
9,
10,
11
]
}
Running Shoe
boolean
⚠️
Never Do This: Single Quotes in JSONWriting {'name': 'Alice'} is valid JavaScript but illegal JSON. The JSON spec requires double quotes everywhere β€” keys and string values. JSON.parse("{'name': 'Alice'}") throws SyntaxError: Unexpected token ' in JSON at position 1. Always use double quotes. No exceptions.

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.

CheckoutApiResponse.js Β· JAVASCRIPT
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
// io.thecodeforge β€” JavaScript tutorial

// Real-world scenario: a checkout service returns this order confirmation
// payload to the frontend after a successful purchase.

const orderConfirmation = {
  "orderId": "ORD-2024-88421",
  "status": "confirmed",
  "totalAmountCents": 15498,
  "currency": "USD",

  // Nested object β€” customer info lives inside the order object.
  "customer": {
    "customerId": "USR-10042",
    "email": "alex.morgan@example.com",
    "loyaltyTier": "gold"
  },

  // Nested object with its own nested object β€” shipping details.
  "shippingAddress": {
    "street": "742 Evergreen Terrace",
    "city": "Springfield",
    "stateCode": "IL",
    "postalCode": "62701",
    // Nested geo coordinates for the delivery routing service.
    "geo": {
      "latitude": 39.7817,
      "longitude": -89.6501
    }
  },

  // Boolean flag the frontend uses to decide whether to show a gift message UI.
  "isGiftOrder": false,

  // Null means no promo was applied β€” explicitly stated, not just absent.
  "promoCode": null
  // ^^^ No trailing comma here. This is the last property. Add one and JSON.parse blows up.
};

// Access nested properties using dot notation after parsing.
const jsonPayload = JSON.stringify(orderConfirmation);
const parsed = JSON.parse(jsonPayload);

console.log(parsed.customer.email);              // "alex.morgan@example.com"
console.log(parsed.shippingAddress.geo.latitude); // 39.7817
console.log(parsed.promoCode);                   // null
console.log(parsed.promoCode === null);           // true β€” null is explicit, not undefined
β–Ά Output
alex.morgan@example.com
39.7817
null
true
⚠️
Senior Shortcut: null vs. Omitting the Key EntirelyThere's a meaningful difference between setting a key to null and not including the key at all. null says 'this field exists and has no value.' A missing key says 'we have no information about this field.' In a product API, discountCode: null means 'no discount was applied.' Omitting discountCode entirely might mean the discount system didn't respond in time. Pick one convention per field and document it β€” mixing them silently destroys consumers.

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.

ProductCatalogResponse.js Β· JAVASCRIPT
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
// io.thecodeforge β€” JavaScript tutorial

// Real-world scenario: a product catalog API returns a page of results.
// This is the most common JSON shape you'll encounter β€” an array of objects.

const catalogPage = {
  "page": 1,
  "pageSize": 3,
  "totalResults": 1482,

  // Array of objects β€” each element is a complete product record.
  "products": [
    {
      "productId": "SKU-001",
      "name": "Trail Runner X9",
      "priceInCents": 12999,
      "categories": ["footwear", "outdoor", "running"], // Array inside an object inside an array.
      "available": true
    },
    {
      "productId": "SKU-002",
      "name": "Merino Wool Sock Pack",
      "priceInCents": 2499,
      "categories": ["footwear", "accessories"],
      "available": true
    },
    {
      "productId": "SKU-003",
      "name": "Compression Sleeve",
      "priceInCents": 1799,
      "categories": ["recovery"],
      "available": false
      // No trailing comma β€” this is the last property in the last object.
    }
    // No trailing comma after the last object in the array either.
  ]
};

const jsonString = JSON.stringify(catalogPage, null, 2);
const parsed = JSON.parse(jsonString);

// Iterate over the array of objects β€” the bread and butter of frontend development.
parsed.products.forEach((product, index) => {
  // Template literals for readable output β€” note the zero-based index.
  console.log(`[${index}] ${product.name} β€” $${(product.priceInCents / 100).toFixed(2)} β€” ${product.available ? 'In Stock' : 'Out of Stock'}`);
});

// Safely access a nested array inside an object inside an array.
console.log(parsed.products[0].categories[1]); // "outdoor" β€” zero-indexed all the way down
β–Ά Output
[0] Trail Runner X9 β€” $129.99 β€” In Stock
[1] Merino Wool Sock Pack β€” $24.99 β€” In Stock
[2] Compression Sleeve β€” $17.99 β€” Out of Stock
outdoor
⚠️
Production Trap: Arrays That Return null Instead of []When a list is empty, always return an empty array [] β€” never null or omit the key. If your products API returns "products": null when there are no results, every consumer has to add a null check before calling .forEach() or .map(), and someone will forget. That someone will cause an Uncaught TypeError: Cannot read properties of null (reading 'forEach') in production at 11pm on a Friday. Return []. 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.

JsonErrorDiagnostics.js Β· JAVASCRIPT
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
// io.thecodeforge β€” JavaScript tutorial

// Six real JSON mistakes with their exact error messages and fixes.
// Run each JSON.parse() call individually to see the error β€” they're isolated below.

// ─── MISTAKE 1: Trailing comma after the last property ───────────────────────
const trailingComma = '{"name": "Alice", "age": 30,}';
// SyntaxError: Unexpected token } in JSON at position 28
// Fix: Remove the comma after 30. JSON doesn't allow trailing commas. Ever.
try {
  JSON.parse(trailingComma);
} catch (e) {
  console.log('Mistake 1:', e.message);
}

// ─── MISTAKE 2: Single quotes instead of double quotes ───────────────────────
const singleQuotes = "{'name': 'Alice'}";
// SyntaxError: Unexpected token ' in JSON at position 1
// Fix: Replace all single quotes with double quotes.
try {
  JSON.parse(singleQuotes);
} catch (e) {
  console.log('Mistake 2:', e.message);
}

// ─── MISTAKE 3: Unquoted key ─────────────────────────────────────────────────
const unquotedKey = '{name: "Alice"}';
// SyntaxError: Unexpected token n in JSON at position 1
// Fix: Wrap the key in double quotes: {"name": "Alice"}
try {
  JSON.parse(unquotedKey);
} catch (e) {
  console.log('Mistake 3:', e.message);
}

// ─── MISTAKE 4: Comment inside JSON ──────────────────────────────────────────
const withComment = '{"name": "Alice" /* the admin user */}';
// SyntaxError: Unexpected token / in JSON at position 17
// Fix: JSON has no comment syntax. Strip all comments before parsing.
try {
  JSON.parse(withComment);
} catch (e) {
  console.log('Mistake 4:', e.message);
}

// ─── MISTAKE 5: undefined as a value ─────────────────────────────────────────
// undefined is a JavaScript concept β€” it doesn't exist in JSON.
// JSON.stringify() silently drops keys with undefined values.
const objectWithUndefined = {
  username: "alice",
  sessionToken: undefined // This key will vanish during serialisation.
};
const serialised = JSON.stringify(objectWithUndefined);
console.log('Mistake 5 β€” undefined silently dropped:', serialised);
// Output: {"username":"alice"} β€” sessionToken is GONE. No error. No warning.
// Fix: Use null for intentional absence. Use a default string like "" if the
// field must always be present.

// ─── MISTAKE 6: Type confusion β€” price as string instead of number ────────────
const orderA = JSON.parse('{"itemPriceCents": "1999"}'); // String β€” looks fine.
const orderB = JSON.parse('{"itemPriceCents": 499}');    // Number β€” correct.

// This is the silent killer. No parse error. Wrong result.
const wrongTotal = orderA.itemPriceCents + orderB.itemPriceCents;
console.log('Mistake 6 β€” string + number:', wrongTotal); // '1999499' β€” not 2498!

// Fix: Always validate and coerce types on ingestion.
const correctTotal = Number(orderA.itemPriceCents) + orderB.itemPriceCents;
console.log('Mistake 6 β€” fixed:', correctTotal); // 2498
β–Ά Output
Mistake 1: Unexpected token '}' is not valid JSON
Mistake 2: Unexpected token '\'' is not valid JSON
Mistake 3: Unexpected token 'n' is not valid JSON
Mistake 4: Unexpected token '/' is not valid JSON
Mistake 5 β€” undefined silently dropped: {"username":"alice"}
Mistake 6 β€” string + number: 1999499
Mistake 6 β€” fixed: 2498
⚠️
The Classic Bug: JSON.stringify() Silently Eats Your DataJSON.stringify() doesn't throw when it hits undefined, functions, or Symbol values β€” it silently drops them. If your object has a method or an undefined field, those keys disappear without a word. Always log the stringified output in development and compare it against the original object shape. The bug you can't see is worse than the bug that throws.
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

  • The no-trailing-comma rule is permanent and non-negotiable β€” memorise it, lint for it, and save yourself hours of debugging parser errors that point to the closing brace instead of the actual offending comma.
  • JSON.stringify() silently drops undefined values and functions without throwing β€” your object shape and your JSON shape can diverge without any error, which is why you validate your serialised output in tests, not just your source objects.
  • Return [] not null for empty lists β€” every API that returns null for an empty collection forces every consumer to add a defensive null check, and someone will always forget, causing a TypeError in production.
  • Valid JSON is not the same as correct JSON β€” a price field serialised as the string '9999' instead of the number 9999 parses without error but will corrupt any arithmetic downstream, which is why schema validation with Zod or Ajv at API boundaries is non-optional on production systems.

⚠ Common Mistakes to Avoid

  • βœ•Mistake 1: Adding a trailing comma after the last property in an object or the last element in an array β€” SyntaxError: Unexpected token } in JSON at position N β€” Remove the trailing comma; JSON.parse() enforces this with zero tolerance, unlike JavaScript which allows it in object literals since ES5.
  • βœ•Mistake 2: Using single quotes for keys or string values instead of double quotes β€” SyntaxError: Unexpected token ' in JSON at position 1 β€” Do a global find-and-replace for single-quoted strings before parsing, or use a linter like eslint-plugin-json that catches this at write time.
  • βœ•Mistake 3: Passing a JavaScript object with undefined values through JSON.stringify() and assuming all keys survive β€” No error thrown, keys with undefined values are silently dropped from the output β€” Audit the stringified result explicitly in tests, or replace undefined with null using a replacer function: JSON.stringify(obj, (key, val) => val === undefined ? null : val).
  • βœ•Mistake 4: Storing numbers as strings in JSON payloads ('priceInCents': '1999' instead of 'priceInCents': 1999) β€” No parse error but arithmetic produces string concatenation instead of addition, e.g. '1999' + 499 = '1999499' β€” Enforce schema validation with a library like Zod or Ajv on every API boundary so type mismatches are caught on ingestion, not in a post-mortem.
  • βœ•Mistake 5: Trying to include a JavaScript Date object directly in JSON β€” JSON.stringify() converts it to an ISO 8601 string automatically, but JSON.parse() brings it back as a plain string, not a Date β€” Always parse date strings explicitly: new Date(parsed.createdAt), and document in your API contract that all timestamps are ISO 8601 strings.

Interview Questions on This Topic

  • QJSON.stringify() and JSON.parse() seem symmetrical β€” but what data types does stringify silently transform or drop that parse can never recover, and how would you design a serialisation layer that handles those cases safely?
  • QYour team receives a third-party webhook payload that's technically valid JSON but uses inconsistent types β€” sometimes a price field is a number, sometimes a string. You can't change the vendor. How do you defend your service against this at the boundary without polluting business logic throughout your codebase?
  • QWhat happens when you call JSON.parse() on a deeply nested JSON string with circular references after accidentally running JSON.stringify() on a JavaScript object that contains circular references β€” and what's the exact error you'd see at each stage?

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 β€” it doesn't produce partial output, it just crashes. 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 or safe-json-stringify, or to use structured logging libraries like pino that handle circular references internally.

πŸ”₯
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousObject Methods in JavaScript
Forged with πŸ”₯ at TheCodeForge.io β€” Where Developers Are Forged