JSON: формат обмена, а не «дамп PHP-переменной»
JSON в PHP — это основной формат для обмена структурированными данными между бэкендом, браузером, мобильным приложением, очередью или внешним API. Он текстовый, переносимый и не привязан к PHP-классам. Поэтому его удобно отдавать наружу через HTTP, хранить в логах, подписывать, передавать между сервисами и читать без PHP-рантайма.
Главная граница такая: json_encode() превращает PHP-значение в JSON-строку, json_decode() превращает JSON-строку обратно в PHP-значение. Это не то же самое, что serialize(): JSON описывает данные, а PHP serialization описывает PHP-структуру со всеми локальными деталями языка.
flowchart TD
A[PHP-значение: array, object, scalar] --> B[json_encode]
B --> C[JSON-строка UTF-8]
C --> D[HTTP API, лог, очередь, файл]
D --> E[json_decode]
E --> F[PHP array или stdClass]
G[serialize] --> H[PHP-specific байтовая строка]
H --> I[Только доверенное внутреннее хранение]
I --> J[unserialize]
J --> K[PHP-значение]
C -. переносимый формат .-> D
H -. не публичный API .-> I<?php
$payload = [
'id' => 42,
'title' => 'Словарь PHP',
'published' => true,
'tags' => ['php', 'json', 'api'],
];
$json = json_encode($payload, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
echo $json . PHP_EOL;На выходе будет нормальная JSON-строка с русским текстом без \uXXXX-экранирования:
{"id":42,"title":"Словарь PHP","published":true,"tags":["php","json","api"]}json_encode(): массив, объект и UTF-8
json_encode() принимает почти любое PHP-значение, кроме resource, и возвращает строку JSON или false, если не включён режим исключений. Для реального кода почти всегда лучше включать JSON_THROW_ON_ERROR, чтобы ошибка кодирования не потерялась среди обычных значений. Это тот же стиль мышления, что и в Ошибки, исключения и Throwable: ошибка должна быть явной.
<?php
try {
$json = json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
} catch (JsonException $e) {
// Логируем техническую причину, наружу отдаём безопасное сообщение.
throw new RuntimeException('Не удалось подготовить JSON-ответ', previous: $e);
}Все строки для JSON должны быть в UTF-8. Если в массив случайно попали байты в другой кодировке, кодирование может упасть. Это напрямую связано со статьёй Строки, UTF-8 и mbstring: PHP-строка сама по себе — набор байтов, а JSON требует понятной Unicode-модели.
Важная деталь из Массивы как ordered map: PHP array может быть списком или ассоциативной картой. В JSON это разные формы: список становится [], ассоциативный массив — {}. Если у списка пропали последовательные ключи, он тоже может стать объектом.
<?php
echo json_encode(['a', 'b', 'c'], JSON_THROW_ON_ERROR) . PHP_EOL;
// ["a","b","c"]
$items = ['a', 'b', 'c'];
unset($items[1]);
echo json_encode($items, JSON_THROW_ON_ERROR) . PHP_EOL;
// {"0":"a","2":"c"}
echo json_encode(array_values($items), JSON_THROW_ON_ERROR) . PHP_EOL;
// ["a","c"]Если вы отдаёте API-массив элементов, после фильтрации часто нужен array_values(), иначе клиент внезапно получит object вместо array.
json_decode(): array или stdClass
json_decode() по умолчанию превращает JSON objects в stdClass. Если вторым аргументом передать true, объекты станут ассоциативными массивами. Для большинства прикладного PHP-кода, где вы валидируете вход и перекладываете его в DTO или обычный массив, true читается проще.
<?php
$raw = '{"id":42,"title":"Словарь PHP"}';
$asObject = json_decode($raw, false, 512, JSON_THROW_ON_ERROR);
echo $asObject->title . PHP_EOL;
$asArray = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
echo $asArray['title'] . PHP_EOL;Параметр depth ограничивает вложенность. Не ставьте его огромным «на всякий случай» для пользовательского ввода: глубоко вложенный JSON может быть способом потратить память и CPU. Если данные пришли из php://input, это уже тема веб-границы из GET, POST и фильтрация ввода и SAPI и суперглобалы: сначала читаем тело запроса, потом декодируем, потом валидируем структуру.
<?php
$body = file_get_contents('php://input');
try {
$input = json_decode($body, true, 64, JSON_THROW_ON_ERROR);
} catch (JsonException) {
http_response_code(400);
echo json_encode(['error' => 'Некорректный JSON'], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
return;
}
if (!is_array($input) || !isset($input['email']) || !is_string($input['email'])) {
http_response_code(422);
echo json_encode(['error' => 'Некорректные поля'], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
return;
}json_validate() появился в PHP 8.3. Он полезен, если нужно только проверить синтаксис JSON и не строить массив или объект в памяти. Но не надо вызывать json_validate() прямо перед json_decode(): декодирование и так проверяет синтаксис, иначе вы просто парсите одну строку два раза.
Числа, строки и флаги
JSON различает string, number, boolean, null, array и object. Но PHP и внешние системы могут по-разному понимать границы чисел. Большие идентификаторы, номера заказов, банковские BIN, телефоны и ZIP-коды лучше хранить строками, даже если они выглядят как числа.
<?php
$json = '{"order_id":12345678901234567890}';
var_dump(json_decode($json, true, 512, JSON_THROW_ON_ERROR));
var_dump(json_decode($json, true, 512, JSON_THROW_ON_ERROR | JSON_BIGINT_AS_STRING));JSON_BIGINT_AS_STRING помогает не потерять точность при декодировании больших integer. А вот JSON_NUMERIC_CHECK при кодировании стоит использовать очень осторожно: он превращает числоподобные строки в numbers. Телефон "+77001234567", артикул "00123" или внешний id могут испортиться.
<?php
$data = [
'phone' => '+77001234567',
'sku' => '00123',
];
echo json_encode($data, JSON_THROW_ON_ERROR | JSON_NUMERIC_CHECK) . PHP_EOL;
// {"phone":77001234567,"sku":123}Для денежных сумм не полагайтесь на float в JSON как на бухгалтерский тип. Часто безопаснее передавать сумму в минимальных единицах (amount_cents: 1299) или строкой с явно оговорённым форматом ("12.99").
Для дат правило из Дата и время остаётся тем же: отдавайте машинный формат вроде RFC 3339, а не локализованную строку «15 июня 2026 г.».
<?php
$createdAt = new DateTimeImmutable('2026-06-15 12:30:00', new DateTimeZone('UTC'));
echo json_encode([
'created_at' => $createdAt->format(DateTimeInterface::RFC3339_EXTENDED),
], JSON_THROW_ON_ERROR);JsonSerializable: явная форма объекта
Если передать объект в json_encode(), по умолчанию попадут только публичные свойства. Это редко хорошая модель для доменного объекта: можно случайно отдать лишнее или, наоборот, получить пустой {}. Лучше явно определить форму JSON через JsonSerializable или собрать массив в отдельном методе.
<?php
final class UserProfile implements JsonSerializable
{
public function __construct(
private int $id,
private string $name,
private string $email,
) {}
public function jsonSerialize(): array
{
return [
'id' => $this->id,
'name' => $this->name,
// email намеренно не отдаём в публичный JSON
];
}
}
echo json_encode(new UserProfile(7, 'Анна', 'anna@example.com'), JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);Это особенно важно рядом с XSS, экранирование вывода и шаблоны: JSON-кодирование не делает данные «безопасными для любого места». JSON внутри <script>, HTML-атрибута, URL и HTTP-ответа — разные контексты вывода.
serialize() и unserialize(): только для доверенной PHP-среды
serialize() создаёт PHP-специфичное байтовое представление значения. Оно умеет сохранять типы, массивы, ссылки и некоторые объекты. Это может быть нормально для внутреннего кеша, сессий или временного состояния, которое читает тот же PHP-код той же версии приложения.
<?php
$state = ['page' => 3, 'filters' => ['active' => true]];
$stored = serialize($state);
var_dump($stored);
var_dump(unserialize($stored, ['allowed_classes' => false]));Но unserialize() нельзя применять к недоверенному пользовательскому вводу. Даже allowed_classes не превращает его в безопасный формат обмена: при десериализации объектов возможны автозагрузка классов, вызовы __wakeup() / __unserialize() и неприятные цепочки в легаси-коде. Для данных, которые уходят наружу или приходят от клиента, используйте JSON и валидируйте схему на своей стороне.
Если вам надо защитить внутренне сохранённую serialized-строку от подмены, минимум — подписывайте её через HMAC и проверяйте подпись до unserialize(). Но это всё ещё внутренний PHP-механизм, не публичный API.
Практические правила
Используйте JSON_THROW_ON_ERROR почти всегда. Для русскоязычных и вообще Unicode-данных добавляйте JSON_UNESCAPED_UNICODE, если нет требования хранить ASCII-only. Для API явно решайте, где объект, где список, и не забывайте array_values() после фильтрации списков.
Не включайте JSON_NUMERIC_CHECK глобально. Большие числа декодируйте с JSON_BIGINT_AS_STRING, если точность важна. Даты отдавайте строками RFC 3339, деньги — строками или integer в минимальных единицах. serialize() оставляйте для доверенной внутренней PHP-инфраструктуры, а не для cookie, URL, внешних webhook и публичных API.
См. также
- Ошибки, исключения и Throwable —
JsonException,RuntimeExceptionи явная обработка ошибок. - Массивы как ordered map — почему PHP array иногда становится JSON object.
- Строки, UTF-8 и mbstring — кодировки, Unicode и байтовая природа строк.
- Дата и время — корректный формат дат для API.
- GET, POST и фильтрация ввода — валидация JSON-тела запроса после декодирования.
- Файловая система и stream wrappers —
php://input, файлы и потоки. - XSS, экранирование вывода и шаблоны — почему JSON не заменяет контекстное экранирование.