PHP JSON: возвращаем данные быстро и без ошибок

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Отладка API на коленке: почему ваш JSON возвращается с ошибкой?

Вы когда-нибудь сидели в три часа ночи, пялились в пустой экран Postman и думали: «Почему мой PHP-скрипт отдаёт HTML вместо JSON?»? Если да — вы не одиноки. Тема Returning JSON from a PHP Script на Stack Overflow собрала десятки тысяч просмотров, и это не случайно. Каждый второй начинающий backend-разработчик наступает на одни и те же грабли: забытые заголовки, мусорный вывод, неверный Content-Type.

В этой статье мы не просто скопируем ответ с форума. Мы разберём, как возвращать JSON из PHP правильно, быстро и без сюрпризов. Вы узнаете, почему exit(json_encode($data)) может вас подвести, как сэкономить миллисекунды на сериализации и что делать, если JSON вдруг «сломался».

Готовы? Поехали.

Анатомия правильного JSON-ответа

Начнём с базы. Вот как не надо делать:

<?php
$data = ['status' => 'ok', 'message' => 'Привет!'];
echo json_encode($data);
?>

Почему это плохо? Во-первых, нет заголовка Content-Type. Браузер или клиент могут воспринять ответ как HTML или текст. Во-вторых, если до этого в коде был какой-то пробел, echo или варнинг — JSON сломается.

Правильный минимальный шаблон выглядит так:

<?php
header('Content-Type: application/json; charset=utf-8');
$data = ['status' => 'ok', 'message' => 'Привет!'];
echo json_encode($data, JSON_UNESCAPED_UNICODE);
exit;
?>

Обратите внимание на JSON_UNESCAPED_UNICODE — он сохраняет кириллицу читаемой, а не превращает её в \u043F\u0440\u0438\u0432\u0435\u0442. И exit — страховка от случайного вывода после ответа.

Заголовки: не только Content-Type

Для оптимизации производительности JSON добавьте ещё пару заголовков:

header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
header('Cache-Control: no-cache, must-revalidate');

X-Content-Type-Options: nosniff запрещает браузеру «угадывать» тип контента — это безопасность. А Cache-Control полезен для динамических данных, чтобы клиент не кэшировал устаревший ответ.

Обработка ошибок: когда JSON_ENCODE падает

Функция json_encode возвращает false при неудаче. И вот тут начинается самое интересное. Если вы просто сделаете echo json_encode($data), а в ответ получите пустую строку — клиент уйдёт в недоумение.

Всегда проверяйте результат:

$json = json_encode($data, JSON_UNESCAPED_UNICODE);
if ($json === false) {
    http_response_code(500);
    echo json_encode(['error' => 'JSON encoding failed: ' . json_last_error_msg()]);
    exit;
}
echo $json;
exit;

Кстати, json_last_error_msg() вернёт понятную строку: «Malformed UTF-8 characters» или «Inf and NaN cannot be JSON encoded». Это спасёт часы дебага.

Глубоко вложенные данные и рекурсия

Ещё одна проблема — циклические ссылки в объектах. Если вы передаёте объект с взаимными ссылками, json_encode выбросит ошибку. Решение: использовать JSON_PARTIAL_OUTPUT_ON_ERROR или предварительно преобразовать объект в массив.

$json = json_encode($complexObject, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR);

Но будьте осторожны: частичный вывод может быть неполным. Лучше чинить структуру данных.

Оптимизация производительности JSON в PHP

Теперь перейдём к самому вкусному — как ускорить возврат JSON из PHP.

1. Кэширование сериализованного JSON

Если данные меняются редко (справочники, конфиги, список городов), не сериализуйте их каждый раз. Сохраните готовый JSON в файл, Memcached или Redis.

// Пример с файлом
$cacheFile = __DIR__ . '/cache/data.json';
if (file_exists($cacheFile) && time() - filemtime($cacheFile) < 3600) {
    $json = file_get_contents($cacheFile);
} else {
    $data = fetchExpensiveData(); // SQL-запрос или API
    $json = json_encode($data, JSON_UNESCAPED_UNICODE);
    file_put_contents($cacheFile, $json, LOCK_EX);
}
header('Content-Type: application/json; charset=utf-8');
echo $json;
exit;

Это даст прирост скорости в десятки раз, особенно если данные собираются из нескольких таблиц.

2. Опции json_encode, которые экономят время

Флаг JSON_UNESCAPED_SLASHES ускоряет сериализацию URL, а JSON_NUMERIC_CHECK преобразует числовые строки в числа — меньше данных для передачи.

$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK);

Но не злоупотребляйте: JSON_NUMERIC_CHECK может «испортить» строки вида «01234» (обрежет ведущий ноль).

3. Сжатие на лету

Если JSON большой (сотни килобайт), включите gzip-сжатие. PHP делает это автоматически, если в .htaccess или конфиге сервера прописано:

// Включение сжатия в PHP
ini_set('zlib.output_compression', 'On');
// или через ob_gzhandler
ob_start('ob_gzhandler');

Размер ответа уменьшится в 5-10 раз, а время передачи сократится.

Практический пример: полноценный JSON endpoint

Соберём всё вместе. Допустим, у нас есть API, которое возвращает список пользователей.

<?php
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
header('Cache-Control: no-cache, must-revalidate');

// Включаем сжатие
ob_start('ob_gzhandler');

// Функция для безопасного JSON-ответа
function sendJsonResponse($data, int $statusCode = 200): void {
    http_response_code($statusCode);
    $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    if ($json === false) {
        http_response_code(500);
        $json = json_encode(['error' => 'Internal server error: ' . json_last_error_msg()]);
    }
    echo $json;
    ob_end_flush();
    exit;
}

// Пример: получаем данные из БД
try {
    $pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pass');
    $stmt = $pdo->query('SELECT id, name                

Блоги

Рекомендаций по книгам