Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Malformed JSON in API Responses: Handling Strategies

Malformed JSON is rarely just a parsing problem. In production it usually means one of four things: the upstream API returned HTML instead of JSON, the response body was truncated, the payload contains JavaScript-style values that are not valid JSON, or the data shape drifted from what your client expects. If you call response.json() blindly, any of those cases can turn into a runtime failure.

A robust strategy is to separate transport checks, parsing, repair decisions, and schema validation. That makes it easier to decide when to retry, when to fail fast, and when a small quarantine-style sanitization step is acceptable for a known bad partner API.

What strict JSON still requires

Interoperable JSON is still defined by RFC 8259: strings use double quotes, trailing commas are not allowed, comments are not part of JSON, and non-finite numbers such as NaN,Infinity, and -Infinity are invalid. Current MDN docs also note thatJSON.parse() throws SyntaxError for illegal JSON, andResponse.json() rejects if the body cannot be parsed.

What Malformed API JSON Usually Looks Like

Search traffic for this topic usually comes from a few repeat offenders. These are the cases worth handling explicitly:

  • HTML or plaintext error pages: A proxy, CDN, or app server returns markup or text while your client still expects JSON.
  • Truncated bodies: The response cuts off mid-object or mid-array because of upstream crashes, timeouts, or connection resets.
  • Almost-JSON: The payload contains trailing commas, comments, single quotes, or other syntax copied from JavaScript instead of strict JSON.
  • Invalid numeric values: Bare NaN, Infinity, or-Infinity show up in the response even though they are not legal JSON numbers.
  • Interoperability bugs: Duplicate object keys or encoding issues make the payload technically parseable in one environment but unreliable across others.

A Fast Triage Checklist Before Parsing

Do these checks before blaming JSON.parse(). They quickly tell you whether this is a parser problem or an upstream contract problem.

  1. Check the HTTP status first. A 502, 503, or 504 with a body is usually not a JSON parser bug.
  2. Inspect the Content-Type header, but do not trust it completely. JSON APIs commonly useapplication/json, application/problem+json, or anotherapplication/*+json media type.
  3. Read the raw text when the endpoint is flaky so you can log a safe preview of what actually arrived.
  4. Distinguish parse failures from validation failures. Invalid syntax and wrong data shape need different fixes.
  5. Log request IDs and response previews, but avoid storing full sensitive payloads in production logs.

Use a Safer Parse Path for Unreliable Endpoints

If an endpoint is known to be inconsistent, prefer response.text() plus an explicit parse step. That gives you better error reporting than jumping straight to response.json(), and it lets you check status codes and media types before parsing.

JavaScript example:

function isJsonMediaType(contentType = "") {
  return /\bapplication\/([a-z.+-]*\+)?json\b/i.test(contentType);
}

async function fetchJsonSafely(url, init) {
  const response = await fetch(url, init);
  const contentType = response.headers.get("content-type") ?? "";
  const text = await response.text();

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${text.slice(0, 200)}`);
  }

  if (!isJsonMediaType(contentType)) {
    throw new Error(
      `Expected a JSON media type, got ${contentType || "unknown"}: ${text.slice(0, 200)}`
    );
  }

  try {
    return JSON.parse(text);
  } catch (error) {
    console.error("Malformed JSON response", {
      url,
      contentType,
      preview: text.slice(0, 500),
      length: text.length,
      error: error instanceof Error ? error.message : String(error),
    });

    throw new Error("API returned malformed JSON", { cause: error });
  }
}

This pattern also avoids a common misconception: response.json() parses the body as JSON, but it does not act as a full contract check for you. If the server sends HTML with a misleading header, you still need your own guardrails.

Handling Invalid Numbers Like NaN or Infinity

This is one of the highest-intent failure modes for this page. Strict JSON does not allow NaN,Infinity, or -Infinity. If an upstream API sends those tokens, a strict parser will reject the payload even if the rest of the document looks fine.

Recommended order of operations

Fix the producer first. If you do not control the upstream API and the bad numeric tokens are a known, documented defect, isolate the cleanup step and make the transformation explicit. Do not silently repair arbitrary untrusted payloads.

Sanitizing non-finite numbers in a trusted partner payload:

function replaceNonFiniteNumbers(jsonText) {
  let result = "";
  let inString = false;
  let escaping = false;

  for (let i = 0; i < jsonText.length; ) {
    const char = jsonText[i];

    if (inString) {
      result += char;

      if (char === '"' && !escaping) {
        inString = false;
      }

      const isBackslash = char.charCodeAt(0) === 92;
      escaping = isBackslash && !escaping;

      if (!isBackslash) {
        escaping = false;
      }

      i += 1;
      continue;
    }

    if (char === '"') {
      inString = true;
      result += char;
      i += 1;
      continue;
    }

    if (jsonText.startsWith("-Infinity", i)) {
      result += "null";
      i += 9;
      continue;
    }

    if (jsonText.startsWith("Infinity", i)) {
      result += "null";
      i += 8;
      continue;
    }

    if (jsonText.startsWith("NaN", i)) {
      result += "null";
      i += 3;
      continue;
    }

    result += char;
    i += 1;
  }

  return result;
}

function parseKnownBadPartnerJson(jsonText) {
  const normalized = replaceNonFiniteNumbers(jsonText);
  return JSON.parse(normalized);
}

Replacing invalid numeric tokens with null is only one possible policy. In some systems you may want to reject the payload instead, map those values to strings, or drop the affected records entirely. The important part is to make that policy visible and test it.

Retry, Repair, or Fail Fast?

Different malformed responses deserve different handling. A single catch block is not enough if you want a predictable client.

  • Retry: Good for truncated responses, upstream 502/503/504 errors, or empty bodies from flaky infrastructure. Use exponential backoff and stick to idempotent requests unless you have stronger guarantees.
  • Fail fast: Best when you got an HTML login page, an authorization error, or the wrong endpoint entirely. Repairing those responses hides the real problem.
  • Repair in quarantine: Acceptable for a known partner bug such as bareNaN values, but keep the fix narrow and heavily logged.
  • Graceful fallback: Useful for UI screens that can render cached data or a partial state while the bad response is investigated.

Validate the Shape After Parsing

Parsing success only means the text was valid JSON. It does not mean the payload matches your contract. Add schema validation immediately after parsing so that syntax problems and contract drift stay separate.

Shape validation example with Zod:

import { z } from "zod";

const ProductSchema = z.object({
  id: z.number(),
  name: z.string(),
  price: z.number().nullable(),
  currency: z.string().length(3),
});

async function fetchProduct(productId) {
  const json = await fetchJsonSafely(`/api/products/${productId}`);
  const result = ProductSchema.safeParse(json);

  if (!result.success) {
    console.error("JSON shape mismatch", result.error.flatten());
    throw new Error("API response shape is invalid");
  }

  return result.data;
}

Log Enough to Debug the Upstream Problem

The best malformed-JSON logging gives you enough context to fix the producer without dumping sensitive data into logs.

  • Request URL and HTTP method
  • Status code and Content-Type
  • Correlation or request ID headers
  • A short response preview, capped to a safe length
  • Body length and parse error message
  • A redaction strategy for tokens, email addresses, or payment data

Conclusion

The practical fix for malformed API JSON is not "just add try/catch." Start by separating transport checks from parsing, then decide explicitly whether a bad response should be retried, rejected, or narrowly sanitized. That keeps temporary outages from looking like data bugs and keeps data bugs from turning into silent corruption.

If you only change one thing, make it this: capture the raw body text for bad responses before parsing, then validate the parsed result against a schema. That one shift makes malformed JSON far easier to diagnose and much safer to handle.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool