← All articles

How to Handle Broken JSON in JavaScript: Parse, Repair, Validate

Handle broken JSON in JavaScript with safer JSON.parse wrappers, fetch response checks, localStorage guards, jsonrepair workflows, error positions, schema validation, and browser-local repair.

JSON.parse() is strict by design. It accepts JSON text, not JavaScript object literals, not JSONC, not Python dictionaries, not an LLM answer wrapped in markdown, and not an HTML error page returned by fetch().

That strictness is useful in production because malformed data should fail loudly. The mistake is treating every parse failure the same way. A webhook body from your own API should usually be rejected. A pasted config snippet in a developer tool can often be repaired and shown back to the user. A browser localStorage value might simply be missing and should not be "repaired" at all.

The practical JavaScript workflow is:

  1. Identify where the text came from.
  2. Strict-parse first with JSON.parse().
  3. If parsing fails, report a useful error with a small context preview.
  4. Repair only when the source is human-pasted, LLM-generated, or exploratory.
  5. Parse the repaired output again.
  6. Validate the resulting value before using it.

Quick Decision Table

Source of the JSON Best response
Your own API returns invalid JSON Reject it, log a safe preview, and fix the producer.
fetch() receives HTML instead of JSON Check status and content-type; fix the route, auth, proxy, or server error.
User pasted almost-JSON into a tool Repair, format, and show the repaired output for review.
LLM output has fences or trailing commas Repair as cleanup, then validate required fields and types.
Hand-edited config contains comments Use JSONC intentionally or repair before strict JSON validation.
localStorage value is null Treat it as missing state; do not pass it blindly to JSON.parse().
[object Object] appears in the input Fix the code that stringified the object incorrectly; the data is already lost.
Payment, permission, migration, or deletion payload is malformed Reject it. Do not auto-repair state-changing input.

This table is the difference between a helpful repair layer and a bug-hiding layer.

Why JSON.parse() Fails

JSON is defined by a small grammar: objects, arrays, strings, numbers, booleans, and null. Property names must be double-quoted strings. Strings must use double quotes. Comments are not allowed. Trailing commas are not allowed. Values such as undefined, NaN, Infinity, True, False, and None are not JSON values.

So this JavaScript object literal is not JSON:

{ name: 'Ada', active: true, }

Strict JSON looks like this:

{
  "name": "Ada",
  "active": true
}

If you pass the first snippet to JSON.parse(), JavaScript is not evaluating it as code. It is parsing it as JSON text. The unquoted key, single quotes, and trailing comma are all syntax errors.

Common Broken JSON Patterns in JavaScript Work

JavaScript object literal copied as JSON

This is the most common "broken JSON" source in front-end code, tests, browser console snippets, and docs:

{
  name: 'Ada Lovelace',
  roles: ['admin', 'editor'],
}

The valid JSON version is:

{
  "name": "Ada Lovelace",
  "roles": ["admin", "editor"]
}

Do not use eval() to make the first version work. If the source is trusted code, keep it as JavaScript. If the source is supposed to be JSON, repair it into strict JSON before parsing.

JSONC comments in a strict JSON parser

Many editor config files look like JSON but are actually JSONC:

{
  // Used only in local development.
  "debug": true,
  "retries": 3,
}

JSON.parse() rejects both the comment and the trailing comma. If your project intentionally uses JSONC, parse it with a JSONC-aware parser. If an API expects strict JSON, remove comments and trailing commas before sending it.

Python or LLM literals

Cross-language snippets often use Python values:

{
  "active": True,
  "deleted": False,
  "metadata": None
}

Strict JSON uses lowercase booleans and null:

{
  "active": true,
  "deleted": false,
  "metadata": null
}

LLM output adds another common wrapper: markdown code fences. A repair step can strip a full fenced block, but your code should still validate the final object before trusting it.

Truncated strings or copied partial responses

This is not just a syntax issue; the data may be incomplete:

{
  "items": [
    { "id": 1, "name": "Ada" },
    { "id": 2, "name": "Grace"

A repair parser can close the open braces so you can inspect the shape. It cannot prove the original response was complete. For API data, fetch the payload again or fix the transport issue.

HTML returned by fetch()

If a response starts with <, the body is probably HTML:

<!doctype html>
<html>
  <body>Not found</body>
</html>

That is not broken JSON. It is the wrong response. The fix is usually the URL, auth header, proxy, route, CDN rule, or server error handler. See Unexpected token < in JSON for the deeper version.

undefined, missing values, and localStorage

JSON.parse(undefined) throws because undefined is converted to the string "undefined" before parsing. localStorage.getItem(key) returns null for a missing key, but your own wrapper or fallback may still produce undefined.

Guard missing values before parsing:

function readJsonFromStorage(key, fallback) {
  const text = window.localStorage.getItem(key);
  if (text === null) return fallback;

  return JSON.parse(text);
}

If the stored value is the literal string "undefined", the storage write path is the bug. Remove the key or write JSON.stringify(value).

A Safe JSON.parse() Wrapper

Start with strict parsing. Return a result object instead of throwing through your UI or request handler:

function parseJsonResult(text) {
  if (typeof text !== "string") {
    return {
      ok: false,
      kind: "not_string",
      message: `Expected a JSON string, received ${typeof text}`
    };
  }

  try {
    return { ok: true, value: JSON.parse(text) };
  } catch (error) {
    return {
      ok: false,
      kind: "syntax",
      message: error instanceof Error ? error.message : String(error),
      detail: describeJsonParseError(text, error)
    };
  }
}

The extra typeof guard catches a surprising number of bugs: objects passed directly to JSON.parse(), missing function returns, unset environment variables, and undefined state during async loading.

Turn Error Positions Into Useful Context

V8-based runtimes such as Chrome and Node often include position 42 in JSON.parse() messages. Firefox tends to report line and column directly. Do not depend on one exact message across every engine, but use the position when it exists:

function describeJsonParseError(text, error) {
  const message = error instanceof Error ? error.message : String(error);
  const match = message.match(/position (\d+)/);
  if (!match) return { message };

  const position = Number(match[1]);
  const before = text.slice(0, position);
  const line = before.split("\n").length;
  const lastNewline = before.lastIndexOf("\n");
  const column = position - lastNewline;
  const start = Math.max(0, position - 40);
  const end = Math.min(text.length, position + 40);

  return {
    message,
    position,
    line,
    column,
    character: JSON.stringify(text[position] ?? ""),
    preview: JSON.stringify(text.slice(start, end))
  };
}

Use JSON.stringify() for the character and preview. It makes tabs, newlines, quotes, backslashes, and invisible control characters visible in logs.

For production logs, keep previews short and redact obvious secrets before sending them to your logging system.

Handle JSON from fetch() Without Guessing

response.json() is convenient, but it hides the raw body once parsing fails. When debugging or building a defensive API client, read text first:

async function readJsonResponse(response) {
  const contentType = response.headers.get("content-type") || "";
  const text = await response.text();

  if (!response.ok) {
    return {
      ok: false,
      kind: "http_error",
      status: response.status,
      preview: text.slice(0, 200)
    };
  }

  if (!contentType.includes("application/json")) {
    return {
      ok: false,
      kind: "not_json_response",
      contentType,
      preview: text.slice(0, 200)
    };
  }

  return parseJsonResult(text);
}

This makes Unexpected token < much easier to diagnose. You can see whether the server returned an HTML login page, a 404 document, or a reverse-proxy error.

Do not repair server responses silently. If your own endpoint emits malformed JSON, fix the serializer or error handler. If a third-party endpoint does it repeatedly, treat that integration as unreliable and handle it explicitly.

Repair Only the Right Inputs

Repair is useful when the source is approximate text:

  • A user pasted a JavaScript object literal into a JSON tool.
  • An LLM returned valid-looking JSON with a trailing comma.
  • A documentation snippet includes comments.
  • A copied config file uses Python True, False, or None.
  • A partial sample needs to be inspected, not committed.

Repair is risky when the input controls a side effect:

  • Granting permissions.
  • Charging money.
  • Updating production records.
  • Running migrations.
  • Deleting resources.
  • Changing security policy.

For state-changing operations, reject invalid JSON and fix the producer. Syntax repair should not become a hidden business-rule decision.

Using jsonrepair in JavaScript

For JavaScript projects, jsonrepair is a practical repair library when you have a full string in memory:

import { jsonrepair } from "jsonrepair";

function repairThenParse(text) {
  const strict = parseJsonResult(text);
  if (strict.ok) {
    return {
      ok: true,
      repaired: false,
      value: strict.value
    };
  }

  try {
    const repairedText = jsonrepair(text);
    return {
      ok: true,
      repaired: true,
      repairedText,
      value: JSON.parse(repairedText)
    };
  } catch (error) {
    return {
      ok: false,
      kind: "repair_failed",
      parseError: strict,
      repairError: error instanceof Error ? error.message : String(error)
    };
  }
}

Notice the order:

  1. Strict parse first.
  2. Repair only after strict parse fails.
  3. Strict parse the repaired text.
  4. Return whether repair happened.

That last flag matters. Your UI, logs, or API client can treat repaired data differently from originally valid JSON.

For huge documents or streaming data, use a streaming parser such as clarinet for strict parsing. Repair is usually easier when you have the complete text; streaming repair is a different problem because the missing bracket or quote may arrive in a later chunk.

Validate the Value After Parsing

Valid JSON syntax is not the same as valid application data.

This parses:

{
  "id": null,
  "email": 123,
  "roles": "admin"
}

But it probably does not match your user model. After parsing or repair, validate the shape:

function validateUser(value) {
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
    return { ok: false, message: "Expected an object" };
  }

  if (typeof value.id !== "string") {
    return { ok: false, message: "Expected id to be a string" };
  }

  if (typeof value.email !== "string") {
    return { ok: false, message: "Expected email to be a string" };
  }

  if (!Array.isArray(value.roles)) {
    return { ok: false, message: "Expected roles to be an array" };
  }

  return { ok: true, value };
}

In a real application, you may use JSON Schema, Zod, Valibot, TypeBox, or another validator. The important part is the boundary: parse handles syntax; validation handles meaning.

Repairs That Are Usually Safe vs Risky

Repair Risk level Why
Remove trailing comma Low Usually a pure syntax mistake.
Convert single quotes to double quotes Low to medium Safe when quotes are balanced; review strings with apostrophes.
Quote unquoted keys Low Common JavaScript-object-literal mistake.
Remove comments Medium Syntax becomes valid, but comment context is lost.
Convert True, False, None Low to medium Common Python-to-JSON cleanup.
Convert undefined to null Medium JSON has no undefined, but null may not mean the same thing.
Close missing braces or brackets Medium to high Useful for inspection, risky for final data.
Convert NaN or Infinity to null or a number High The value meaning changes. Flag it.
Strip leading or trailing prose High The repair tool may be guessing where data begins or ends.

A good UI shows the repaired text and lets the user review it. For anything important, compare before and after with JSON Diff.

Do Not Use eval() as a JSON Repair Tool

It is tempting to "parse" JavaScript-like JSON with eval() or new Function():

// Do not do this with untrusted input.
const value = eval(`(${text})`);

That executes code. If the input came from a user, a clipboard, a log, a support ticket, or an LLM, it is not safe. Even for trusted internal snippets, using JavaScript evaluation blurs the boundary between data and code.

Use a parser or repair parser. Data should stay data.

Browser-Local Repair Is Better for Sensitive JSON

Broken JSON often contains exactly the values you should not upload casually:

  • API keys and bearer tokens.
  • Cookies and session IDs.
  • Customer emails and internal IDs.
  • Webhook bodies and signatures.
  • Logs with request headers.
  • Environment dumps and service URLs.

Browser-local repair reduces that risk because the text can be parsed and repaired inside the page without being posted to a server. JSON Fix is built around that workflow.

Still redact before sharing screenshots, tickets, pull requests, or chat messages. Local processing protects the repair step; it does not make secrets safe to publish.

A Production Pattern

For application code, keep the policy explicit:

function parseIncomingJson(text, options = { allowRepair: false }) {
  const parsed = parseJsonResult(text);
  if (parsed.ok) {
    return { ok: true, repaired: false, value: parsed.value };
  }

  if (!options.allowRepair) {
    return parsed;
  }

  return repairThenParse(text);
}

Then decide at the call site:

const apiPayload = parseIncomingJson(rawApiBody, { allowRepair: false });
const pastedSample = parseIncomingJson(userClipboardText, { allowRepair: true });

That tiny option prevents repair from spreading into code paths where invalid JSON should be treated as a real defect.

Use JSON Fix for Pasted Broken JSON

If you have a malformed sample right now, paste it into JSON Fix. It can repair common syntax mistakes such as trailing commas, single quotes, unquoted keys, comments, Python literals, undefined, markdown fences, and unclosed structures. The repair and formatting run in your browser.

After repair:

  1. Review the output.
  2. Validate it with JSON Validator.
  3. Open it in JSON Viewer if the structure is large.
  4. Compare the before and after with JSON Diff if the data matters.
  5. Fix the producer if the same malformed JSON keeps appearing.

Frequently Asked Questions

How do I handle a JSON.parse error in JavaScript?

Wrap JSON.parse() in try/catch, return a structured result, and include a short error preview instead of letting the SyntaxError crash the UI or request handler. Also check that the input is actually a string before parsing.

Can JavaScript parse broken JSON automatically?

Not with JSON.parse(). Use a repair parser such as jsonrepair when the input is human-pasted, LLM-generated, or otherwise approximate. After repair, parse the repaired string again with JSON.parse().

When should I repair JSON vs reject it?

Repair pasted examples, developer-tool input, and LLM output after review. Reject malformed JSON from API contracts, payments, permissions, migrations, deletes, and other state-changing workflows.

How do I handle broken JSON from fetch?

Read the response as text, check response.ok, inspect the content-type, and only parse as JSON when the response is actually JSON. If the body starts with HTML, fix the route, auth, proxy, or server error instead of repairing it.

What is the most common cause of broken JSON?

The most common cause is treating a JavaScript object literal as JSON: single quotes, unquoted keys, trailing commas, comments, and values such as undefined, NaN, or Infinity.

Is it safe to use eval to parse almost-JSON?

No. eval() and new Function() execute code. Use a JSON parser, JSONC parser, or repair parser instead, especially for user input, logs, clipboard text, support tickets, and LLM output.

Should I validate after repairing JSON?

Yes. Repair fixes syntax, not business meaning. Validate required fields, types, allowed values, and schema rules before using repaired data in an application.

Can JSON repair recover [object Object]?

Usually no. [object Object] means a JavaScript object was already coerced to a string, and the original keys and values are gone. Fix the serialization path with JSON.stringify() before the data is lost.

Related Tools & Guides

Sources

Last reviewed July 2026.