JSON в PHP
JSON - это язык дипломатии между фронтом и бэком. Иногда, конечно, это дипломатия уровня «прислали строку вместо числа», но всё же. Сам формат - это объекты-«словари» и массивы (см. урок про массивы); подробное описание формата - в JSON-кросс-уроке.
json_encode - из PHP в JSON
<?php
$data = [
'ok' => true,
'user' => ['id' => 1, 'name' => 'Иван'],
];
echo json_encode($data);
// {"ok":true,"user":{"id":1,"name":"Иван"}}
Без флагов кириллица превращается в \uXXXX. Добавляем флаги:
<?php
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
// {
// "ok": true,
// "user": {
// "id": 1,
// "name": "Иван"
// }
// }
Полезные флаги json_encode
<?php
$flags = JSON_UNESCAPED_UNICODE // кириллица как есть
| JSON_UNESCAPED_SLASHES // / без экранирования
| JSON_PRETTY_PRINT // форматированный вывод (для отладки)
| JSON_THROW_ON_ERROR; // исключение вместо молчаливого false
echo json_encode($data, $flags);
json_decode - из JSON в PHP
<?php
$json = '{'id':1,'name':'Иван','scores':[95,88,72]}';
// Второй аргумент true - вернуть массив (по умолчанию - объект)
$data = json_decode($json, true);
echo $data['name']; // Иван
echo $data['scores'][0]; // 95
// Без true - вернёт stdClass
$obj = json_decode($json);
echo $obj->name; // Иван
echo $obj->scores[0]; // 95
Обработка ошибок
Старый способ (до PHP 7.3):
<?php
$data = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
echo 'Ошибка: ' . json_last_error_msg();
}
Современный способ (PHP 7.3+) - через исключения:
<?php
try {
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
echo 'Плохой JSON: ' . $e->getMessage();
}
Используй JSON_THROW_ON_ERROR - это чище и надёжнее. Работает и в json_encode, и в json_decode.
Отправка JSON-ответа
Типичный паттерн для API-эндпоинта (полный REST с правильными статус-кодами - в уроке REST API):
<?php
// Устанавливаем заголовок ДО вывода
header('Content-Type: application/json; charset=utf-8');
$response = [
'ok' => true,
'data' => [
['id' => 1, 'title' => 'Купить молоко', 'done' => false],
['id' => 2, 'title' => 'Написать код', 'done' => true],
],
];
echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
exit;
Для ошибок:
<?php
header('Content-Type: application/json; charset=utf-8');
http_response_code(400);
echo json_encode([
'ok' => false,
'error' => 'Параметр id обязателен',
], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
exit;
Чтение JSON из тела запроса
Когда фронтенд отправляет JSON через fetch или axios:
<?php
// Читаем сырое тело запроса
$rawBody = file_get_contents('php://input');
try {
$data = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
http_response_code(400);
echo json_encode(['error' => 'Невалидный JSON']);
exit;
}
// Теперь $data - обычный PHP-массив
$title = $data['title'] ?? '';
Работа с JSON-файлами
Чтение конфигурации из файла:
<?php
$configJson = file_get_contents(__DIR__ . '/config.json');
$config = json_decode($configJson, true, 512, JSON_THROW_ON_ERROR);
echo $config['database']['host']; // localhost
Запись в файл:
<?php
$data = ['users' => [['id' => 1, 'name' => 'Иван']]];
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
file_put_contents(__DIR__ . '/data.json', $json);
Вложенные данные и глубина
Третий аргумент json_decode - максимальная глубина вложенности (по умолчанию 512):
<?php
// Если JSON очень глубоко вложен
$deep = json_decode($json, true, 10, JSON_THROW_ON_ERROR);
// JsonException если вложенность > 10 уровней
Типичные подводные камни
<?php
// 1. Числа в строках
$json = '{'price':'199'}';
$data = json_decode($json, true);
echo $data['price'] + 1; // 200 - PHP приведёт, но лучше явно: (int)$data['price']
// 2. Null vs отсутствие ключа
$json = '{'name':null}';
$data = json_decode($json, true);
isset($data['name']); // false! (значение null)
array_key_exists('name', $data); // true
// 3. json_encode не принимает невалидный UTF-8
$bad = "Привет \x80 мир"; // битый UTF-8
json_encode($bad); // false!
json_encode($bad, JSON_THROW_ON_ERROR); // JsonException
Хелпер-функции
В реальных проектах удобно сделать обёртки:
<?php
declare(strict_types=1);
function jsonResponse(array $data, int $status = 200): never {
http_response_code($status);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
exit;
}
function jsonError(string $message, int $status = 400): never {
jsonResponse(['ok' => false, 'error' => $message], $status);
}
// Использование:
// jsonResponse(['ok' => true, 'users' => $users]);
// jsonError('Пользователь не найден', 404);
Типичные ошибки
- Забыл
JSON_THROW_ON_ERROR.json_decodeмолча возвращаетnullпри битом JSON, иnullнеотличим от валидного"null". Всегда передавай флаг или проверяй черезjson_last_error(). json_encodeбезJSON_UNESCAPED_UNICODE. Кириллица превращается вПри...- валидно, но нечитаемо в логах и раздувает payload вдвое. Добавляй флаг во все API-ответы.- Путаница
assoc=truevsfalse. Без второго аргументаjson_decodeвозвращаетstdClass, и$data['name']падает с ошибкой. Для API-данных всегда передавайtrue- массивы предсказуемее. - Кодирование объектов с приватными полями.
json_encodeвидит только публичные свойства, приватные молча выпадают. РеализуйJsonSerializable::jsonSerialize()или используй DTO с публичными полями (или Symfony Serializer с groups). - Загрузка большого JSON через
json_decode. Файл на 500 МБ съест всю память процесса. Для больших потоков используйhalaxa/json-machineилиpcrov/jsonreader- они парсят итеративно.
Best practices
- Всегда передавай
JSON_THROW_ON_ERRORвjson_encodeиjson_decode. Молчаливыеfalse/null- главный источник багов в API. - В API-ответах ставь
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES. Меньше байтов в сети и читаемые логи. - Для контроллеров используй
Symfony\Component\HttpFoundation\JsonResponseили Symfony Serializer - они сами проставятContent-Type, кодировку и обработают ошибки. - DTO с публичными типизированными свойствами вместо
stdClass. IDE подсказывает поля, статанализ ловит опечатки,JsonSerializableдаёт контроль над форматом. - Ограничивай глубину третьим аргументом
json_decode($json, true, 32, JSON_THROW_ON_ERROR). Защита от DoS-атак с глубоко вложенным payload.
Мини-задание
- Сформируй JSON для списка задач (id, title, done) и выведи с
JSON_PRETTY_PRINT - Распарсь JSON обратно и выведи только невыполненные задачи (
done === false) - Напиши эндпоинт, который читает JSON из
php://inputи возвращает JSON-ответ - Добавь обработку ошибок через
JSON_THROW_ON_ERRORиtry/catch - Создай функцию
jsonResponse()и используй её для ответов API