JSON в PHP: json_encode и json_decode с примерами
Коротко
Для работы с JSON в PHP есть две функции: json_encode() превращает массив или объект в строку JSON, а json_decode() разбирает строку обратно. Чтобы кириллица не превращалась в \u041f, добавьте флаг JSON_UNESCAPED_UNICODE, а для надёжности всегда передавайте JSON_THROW_ON_ERROR: тогда ошибка не потеряется молча.
Содержание
- json_encode: массив в JSON
- Кириллица в json_encode: JSON_UNESCAPED_UNICODE
- Полезные флаги json_encode
- json_decode: JSON в массив или объект
- Ошибки JSON и как их поймать
- Как отдать JSON в ответе: header и код ответа
- Как принять JSON из запроса
- JSON из внешнего API через cURL
- Дробные числа и точность
- Объекты в JSON: JsonSerializable
- Чтение и запись JSON-файла
- JSON в Laravel и быстрая проверка в консоли
json_encode: массив в JSON
Функция json_encode() принимает почти любое значение PHP: массив, объект, строку, число, true, false или null, и возвращает строку. Это основной способ отдать данные в JavaScript или во внешний API, и с него начинается любая работа с PHP JSON.
$user = [
'id' => 7,
'name' => 'Анна',
'email' => 'anna@example.com',
'roles' => ['admin', 'editor'],
'active' => true,
];
echo json_encode($user, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
// {"id":7,"name":"Анна","email":"anna@example.com","roles":["admin","editor"],"active":true}

Как PHP решает, чем станет массив в JSON:
- список с ключами 0, 1, 2... по порядку превращается в массив JSON
[...]; - массив со строковыми ключами или с "дырами" в нумерации превращается в объект
{...}; - объект PHP становится объектом JSON, но попадают в него только публичные свойства.
Подробнее о том, как устроены ключи и списки, рассказано в статье про массивы в PHP.
Кириллица в json_encode: JSON_UNESCAPED_UNICODE
По умолчанию json_encode() записывает все не-ASCII символы последовательностями \uXXXX. Для машины это корректный JSON, но читать такие строки и логи невозможно, а размер ответа растёт в несколько раз.
$data = ['city' => 'Москва'];
echo json_encode($data);
// {"city":"\u041c\u043e\u0441\u043a\u0432\u0430"}
echo json_encode($data, JSON_UNESCAPED_UNICODE);
// {"city":"Москва"}
Флаг работает только с текстом в UTF-8. Если строки пришли из старой базы в windows-1251, json_encode() вернёт false с ошибкой "Malformed UTF-8 characters". Перекодируйте данные заранее:
$name = mb_convert_encoding($rowFromOldDb['name'], 'UTF-8', 'Windows-1251');
Если кодировку исправить нельзя, флаг JSON_INVALID_UTF8_SUBSTITUTE заменит битые байты символом "�", и функция хотя бы вернёт результат.
Полезные флаги json_encode
Флаги объединяются через |. Набор ниже покрывает большинство задач:
| Флаг | Что делает | Когда нужен |
|---|---|---|
JSON_UNESCAPED_UNICODE | Оставляет русские буквы как есть | Почти всегда |
JSON_UNESCAPED_SLASHES | Не экранирует / в адресах | Ссылки и пути в данных |
JSON_PRETTY_PRINT | Переносы строк и отступы в 4 пробела | Конфиги, отладка, файлы |
JSON_THROW_ON_ERROR | Бросает JsonException вместо возврата false | Всегда |
JSON_FORCE_OBJECT | Любой массив пишет как объект | API ждёт только объекты |
JSON_PRESERVE_ZERO_FRACTION | Пишет 10.0, а не 10 | Важен тип числа |
JSON_NUMERIC_CHECK | Строки-числа превращает в числа | Осторожно: "007" станет 7 |
const JSON_FLAGS = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR;
$config = ['site' => 'https://example.com/', 'title' => 'Мой сайт'];
echo json_encode($config, JSON_FLAGS | JSON_PRETTY_PRINT);

json_decode: JSON в массив или объект
Функция json_decode() разбирает строку. По умолчанию она возвращает объекты stdClass, а со вторым аргументом true - обычные ассоциативные массивы, с которыми привычнее работать.
$json = '{"id":7,"name":"Анна","roles":["admin","editor"],"address":{"city":"Москва"}}';
// объект: доступ через стрелку
$obj = json_decode($json, false, 512, JSON_THROW_ON_ERROR);
echo $obj->address->city; // Москва
// массив: доступ через квадратные скобки
$arr = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
echo $arr['address']['city']; // Москва
echo count($arr['roles']); // 2
- третий аргумент - максимальная глубина вложенности, по умолчанию 512;
- флаг
JSON_BIGINT_AS_STRINGсохраняет очень большие числа (например, идентификаторы из внешних API) строкой, иначе они превратятся вfloatи потеряют точность; - JSON
nullи ошибка разбора без флага исключений дают одинаковый результатnull, поэтому безJSON_THROW_ON_ERRORих не различить.

Ошибки JSON и как их поймать
Без флага исключений функции молча возвращают false или null. Узнать причину можно через json_last_error_msg(), но проще и надёжнее ловить JsonException:
$raw = '{"name": "Анна",}'; // лишняя запятая
try {
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
error_log('Плохой JSON: ' . $e->getMessage()); // Syntax error
$data = [];
}
| Сообщение | Причина | Как исправить |
|---|---|---|
| Syntax error | Лишняя запятая, одинарные кавычки, комментарий | Исправить источник, JSON допускает только двойные кавычки |
| Malformed UTF-8 characters | Строка не в UTF-8 | Перекодировать через mb_convert_encoding() |
| Maximum stack depth exceeded | Вложенность больше лимита | Увеличить третий аргумент |
| Control character error | Перевод строки или табуляция внутри строки | Экранировать на стороне источника |
| Inf and NaN cannot be JSON encoded | В данных INF или NAN | Заменить на null до кодирования |
Частая ловушка: в начале строки стоит невидимый BOM от редактора, и разбор падает с "Syntax error", хотя JSON выглядит правильно. Уберите его: $raw = preg_replace('/^\xEF\xBB\xBF/', '', $raw);.
Проверка без разбора: json_validate
С PHP 8.3 есть функция json_validate(). Она только отвечает, корректна ли строка, и тратит меньше памяти, чем полный разбор. Подходит, когда JSON нужно просто проверить и сохранить как есть:
if (!json_validate($payload)) {
http_response_code(422);
exit('Некорректный JSON');
}
Как отдать JSON в ответе: header и код ответа
Чтобы браузер и JavaScript поняли, что пришёл JSON, нужен заголовок Content-Type: application/json. Это и есть запрос "php header json". Заголовок отправляется до любого вывода, иначе PHP выдаст предупреждение "Cannot modify header information".
function sendJson(mixed $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_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
exit;
}
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($id === false || $id === null) {
sendJson(['error' => 'Не передан id'], 400);
}
sendJson(['id' => $id, 'status' => 'ok']);
Если вместо JSON браузер получает пустой ответ или HTML с текстом ошибки, включите вывод ошибок PHP или посмотрите лог: часто в ответ попадает предупреждение, которое ломает разбор на стороне JavaScript.

Как принять JSON из запроса
Когда фронтенд отправляет данные через fetch() с телом JSON, массив $_POST остаётся пустым: PHP заполняет его только для форм. Тело запроса читают из потока php://input:
$raw = file_get_contents('php://input');
try {
$input = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException) {
sendJson(['error' => 'Ожидался JSON'], 400);
}
$title = trim((string) ($input['title'] ?? ''));
Код на стороне браузера для такого обработчика:
const response = await fetch('/api/notes.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Купить молоко' })
});
const result = await response.json();
console.log(result.status);
Полный пример таблицы, которая получает данные с сервера в JSON и листает их постранично, разобран в статье про DataTables server-side processing.
JSON из внешнего API через cURL
Ответ чужого сервиса тоже приходит строкой JSON. Перед разбором проверьте код ответа: при ошибке многие API отдают HTML-страницу, и json_decode() на ней споткнётся.
$ch = curl_init('https://api.example.com/v1/rates?base=RUB');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Accept: application/json'],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($body === false || $status !== 200) {
throw new RuntimeException("API ответил кодом {$status}");
}
$rates = json_decode($body, true, 512, JSON_THROW_ON_ERROR | JSON_BIGINT_AS_STRING);
echo $rates['data']['USD'] ?? 'нет курса';
Отправка JSON в API выглядит зеркально: тело собирают через json_encode(), передают в CURLOPT_POSTFIELDS и добавляют заголовок Content-Type: application/json. Функцию curl_close() в PHP 8 вызывать не нужно, дескриптор закрывается сам.
Дробные числа и точность
Иногда в JSON вместо 0.1 появляется 0.10000000000000001. За это отвечает директива serialize_precision в php.ini: значение -1 (оно стоит по умолчанию) выводит самое короткое точное представление числа. Если в старом конфиге там 17, верните -1. Для денег лучше хранить копейки целым числом или передавать сумму строкой.
Объекты в JSON: JsonSerializable
Объект с приватными свойствами превратится в пустой {}. Чтобы управлять результатом, реализуйте интерфейс JsonSerializable:
final class Product implements JsonSerializable
{
public function __construct(
private int $id,
private string $title,
private int $priceKop,
) {}
public function jsonSerialize(): array
{
return [
'id' => $this->id,
'title' => $this->title,
'price' => $this->priceKop / 100,
];
}
}
echo json_encode([new Product(1, 'Чайник', 249900)], JSON_UNESCAPED_UNICODE);
// [{"id":1,"title":"Чайник","price":2499}]
Пустой массив PHP кодируется как []. Если API ждёт пустой объект, передайте new stdClass() или (object) []. А если после array_filter() список вдруг стал объектом с ключами "0", "2", "5", пропустите его через array_values().
Чтение и запись JSON-файла
JSON удобен для небольших настроек и кеша. Запись с блокировкой не даст двум запросам испортить файл одновременно:
$file = __DIR__ . '/storage/settings.json';
// запись
$settings = ['theme' => 'dark', 'perPage' => 20];
file_put_contents(
$file,
json_encode($settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
LOCK_EX
);
// чтение
$settings = is_file($file)
? json_decode(file_get_contents($file), true, 512, JSON_THROW_ON_ERROR)
: [];
Для данных, которые часто меняются и ищутся, файл не подходит, их лучше хранить в базе. Как сохранять и читать записи из MySQL, показано в статье PDO в PHP. В MySQL 8 есть и отдельный тип столбца JSON: значение из json_encode() записывается в него как обычная строка через подготовленный запрос.
JSON в Laravel и быстрая проверка в консоли
В Laravel ответ в JSON отдаёт хелпер response()->json(), он сам ставит заголовок. Русские буквы он по умолчанию экранирует, поэтому флаг передают четвёртым аргументом:
return response()->json($users, 200, [], JSON_UNESCAPED_UNICODE);
Вместо онлайн-сервисов для json decode удобно проверить строку прямо в терминале: так данные не уходят на чужой сайт.
php -r 'var_dump(json_decode(file_get_contents("data.json"), true));'
# красиво отформатировать файл
php -r 'echo json_encode(json_decode(file_get_contents("data.json")), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);'

Частые вопросы
Как в PHP преобразовать массив в JSON?
Вызовите json_encode($array, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR). Список с ключами 0, 1, 2 станет массивом JSON, ассоциативный массив станет объектом.
Почему json_encode выводит русские символы как \u0430?
Так работает экранирование по умолчанию. Добавьте флаг JSON_UNESCAPED_UNICODE, и кириллица останется читаемой. Строки при этом должны быть в UTF-8.
Как получить из json_decode массив, а не объект?
Передайте вторым аргументом true: json_decode($json, true). Тогда вложенные объекты тоже станут ассоциативными массивами.
Почему json_decode возвращает null?
Строка некорректна: лишняя запятая, одинарные кавычки, BOM в начале или кодировка не UTF-8. Добавьте флаг JSON_THROW_ON_ERROR и прочитайте текст исключения.
Какой header нужен для ответа JSON в PHP?
header('Content-Type: application/json; charset=utf-8'), отправленный до любого вывода. Код ответа задайте через http_response_code().


