How Do I Fix jQuery ParseJSON Fail When PHP Prints HTML Error Pages

I’ve run into this problem more times than I’d like to admit: I’m happily building an AJAX feature, jQuery is supposed to parse JSON, and suddenly it blows up with a mysterious parsererror.

After digging around, I realized the culprit wasn’t jQuery at all it was PHP quietly printing HTML error pages (often from Xdebug or display_errors=On) before my JSON. Let me walk you through what’s happening, how I fixed it, and a few extra tricks I added along the way.

The Problem Version

Here’s where I started:

function getMyCharities(callback) {
  $.post("my_file.php", function (data) {
    callback(data);  // data may contain HTML before JSON
  });
}

The issue?

  • jQuery (or JSON.parse) expects pure JSON.
  • PHP errors (warnings, notices, fatals) often come out as an HTML table with <xdebug-error> markup.
  • That HTML gets prepended to my JSON.
  • Result: response begins with < instead of { or [, and parsing fails with parsererror.

The Server Side Fix Always Return Clean JSON

The first step was fixing things at the source. I made sure my PHP scripts never leak HTML errors and always return well-formed JSON.

Here’s the pattern I use:

<?php
// my_file.php

// Never print PHP errors to the response; log them instead.
ini_set('display_errors', '0');
ini_set('log_errors', '1');
error_reporting(E_ALL);

// Convert errors and exceptions to JSON
set_exception_handler(function (Throwable $e) {
    http_response_code(500);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode([
        'ok'    => false,
        'error' => [
            'type'    => get_class($e),
            'message' => $e->getMessage(),
        ],
    ]);
    exit;
});

set_error_handler(function ($severity, $message, $file, $line) {
    throw new ErrorException($message, 0, $severity, $file, $line);
});

// Prevent accidental echo/whitespace
ob_start();
header('Content-Type: application/json; charset=utf-8');

try {
    $charities = [
        ['id' => 1, 'name' => 'Doctors Without Borders'],
        ['id' => 2, 'name' => 'Charity: Water'],
    ];

    ob_clean(); // clear buffer
    echo json_encode(['ok' => true, 'data' => $charities], JSON_UNESCAPED_UNICODE);
    exit;
} catch (Throwable $e) {
    throw $e; // delegate to handler
}

Now, no matter what happens, the response is valid JSON. If there’s an error, I return an error object instead of a half-broken HTML page.

The Client Side Fix Handle JSON Safely

On the client, I switched to $.ajax with dataType: 'json' and added guards.

function getMyCharities() {
  return $.ajax({
    url: 'my_file.php',
    method: 'POST',
    dataType: 'json', // jQuery will auto-parse JSON
    timeout: 7000
  })
  .then(function (resp) {
    if (!resp || resp.ok !== true || !Array.isArray(resp.data)) {
      return $.Deferred().reject({ kind: 'bad-shape', resp: resp }).promise();
    }
    return resp.data;
  })
  .fail(function (jqXHR, textStatus) {
    if (textStatus === 'parsererror') {
      console.error('Response was not valid JSON. Check PHP output/Xdebug.');
    } else if (textStatus === 'timeout') {
      console.error('Request timed out.');
    } else {
      console.error('AJAX failed:', textStatus, jqXHR.status);
    }
  });
}

Now my frontend can tell whether the response was good data, bad JSON, or a network/server error.

Practice Enhancement

Once I had the basics working, I added some quality-of-life features:

Retry with Backoff

function fetchWithRetry({ maxTries = 3, baseDelay = 400 } = {}) {
  let attempt = 0;

  function run() {
    attempt++;
    return getMyCharities()
      .then(data => data)
      .catch(err => {
        if (attempt >= maxTries) throw err;
        const wait = baseDelay * Math.pow(2, attempt - 1);
        return new Promise(res => setTimeout(res, wait)).then(run);
      });
  }

  return run();
}

Cancel an In Flight Request

const req = getMyCharities();
$('#cancelBtn').on('click', function () {
  req.abort(); // jqXHR supports abort
});

User Friendly Error Toast

function showErrorToast(err) {
  const msg = (err && err.resp && err.resp.error && err.resp.error.message)
    ? err.resp.error.message
    : 'Something went wrong. Please try again.';
  alert(msg);
}

Salvaging Mixed HTML + JSON

If you can’t fix PHP right away, you can extract the first JSON block from the response. It’s fragile, but it works as a stopgap:

function extractFirstJson(text) {
  const match = text.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
  if (!match) throw new Error('No JSON found in response');
  return JSON.parse(match[1]);
}

$.ajax({
  url: 'my_file.php',
  method: 'POST',
  dataType: 'text'
})
.then(function (raw) {
  const json = extractFirstJson(raw);
  if (!json.ok) throw { resp: json };
  return json.data;
})
.then(renderCharities)
.catch(showErrorToast);

Fix the server. This hack is just duct tape.

Final Thought

The real lesson I learned was this don’t trust PHP to “just print JSON.” I had to build a disciplined pattern where the server always outputs proper JSON and never leaks HTML errors, while the client explicitly requests dataType:'json' and guards against parser errors, timeouts, and schema mismatches.

Related blog posts