JSON в PHP: чтение, запись и лучшие практики

Быстрые ссылки
Reading JSON Data
Handling Parsing Errors
Serializing data to JSON
Bringing JSON to Your Application’s Domain Layer
JSON — один из самых распространённых форматов сериализации данных. Он появился в экосистеме JavaScript (JSON — JavaScript Object Notation) и стал де-факто стандартом для веб-API и конфигурационных файлов.
PHP содержит встроенную поддержку JSON. Раньше это была отдельная расширяемая библиотека, но начиная с PHP 8.0 расширение JSON всегда активно и не может быть отключено.
Основные определения
- JSON: текстовый формат для представления структурированных данных.
- Сериализация: преобразование структуры данных в строку для хранения или передачи.
- Десериализация: обратный процесс — преобразование строки в структуру данных приложения.
Чтение JSON (json_decode)
Для разбора JSON используйте функцию
json_decode()Её сигнатура:
json_decode(string $json, bool|null $associative=null, int $depth=512, int $flags=0) : mixed;Простейший вызов — передать строку JSON без дополнительных аргументов. Пример:
{"foo": "bar"}По умолчанию это декодируется в экземпляр PHP-класса
stdClassс публичным свойством
fooравным
bar.
Если вы хотите получить ассоциативный массив, передайте
trueв параметр
$associative:
["foo" => "bar"]Параметр
$depthопределяет максимальную глубину вложенности. При превышении этой глубины функция вернёт
nullи не попытается разобрать данные.
Параметр
$flagsпринимает битовую маску опций, влияющих на поведение парсера. Полный список флагов и их описание содержится в официальной документации PHP.
Важно: по умолчанию json_decode возвращает null при ошибке, что не позволяет отличить корректный JSON, содержащий значение null, от ошибки разбора. Начиная с PHP 7.3 используется флаг JSON_THROW_ON_ERROR, позволяющий выбрасывать исключение при ошибке.
Обработка ошибок при парсинге
По умолчанию:
json_decode()вернёт
nullпри передаче некорректной JSON-строки. Это проблематично, потому что JSON может легитимно содержать значение null.
Начиная с PHP 7.3, добавлен флаг
JSON_THROW_ON_ERROR. Передайте его в параметр
$flagsчтобы получить исключение при ошибке разбора:
json_decode("['malformed json", true, 512, JSON_THROW_ON_ERROR);Для удобства и читабельности в PHP 8 можно использовать именованные аргументы:
json_decode(json: "['malformed json", flags: JSON_THROW_ON_ERROR);Пример с обработкой исключения:
try {
$data = json_decode($jsonString, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
// Логи, метрики, информирование пользователя/клиента
error_log('JSON decode error: ' . $e->getMessage());
throw $e; // или другая стратегия обработки
}Примечание: JsonException доступен начиная с соответствующей версии PHP; проверяйте совместимость при поддержке старых платформ.
Запись данных в JSON (json_encode)
Для сериализации значений PHP в JSON используется функция
Её сигнатура:
json_encode(mixed $value, int $flags=0, int $depth=512) : string|false;PHP принимает любые значения, кроме ресурсов. Типы автоматически приводятся в соответствующее представление JSON. Объекты и ассоциативные массивы станут JSON-объектами; скаляры — прямыми JSON-значениями.
Обратите внимание: порядок опциональных параметров у json_encode отличается от json_decode (flags идёт перед depth), что часто вызывает путаницу.
Основные флаги кодирования, которые часто полезно знать:
- JSON_FORCE_OBJECT — заставляет числовые массивы преобразовываться в JSON-объекты. Полезно, когда нужно гарантировать объект даже для пустого массива.
- JSON_PRETTY_PRINT — форматирует вывод с отступами и переводами строк для удобства чтения людьми.
- JSON_PRESERVE_ZERO_FRACTION — сохраняет дробную часть у чисел типа 0.0 (иначе может получиться 0).
- JSON_NUMERIC_CHECK — преобразует числовые строки в числа в выходном JSON (например, “234.5” -> 234.5).
- JSON_PARTIAL_OUTPUT_ON_ERROR — пытается продолжить сериализацию, подставляя допустимые значения, если встречаются несерилизуемые элементы.
Пример использования json_encode:
$data = ['id' => 1, 'title' => "Пример"];
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_PRESERVE_ZERO_FRACTION);Важно: не включайте в JSON ресурсы или замыкания — они не сериализуемы и вызовут ошибку. Для частичных ошибок можно использовать JSON_PARTIAL_OUTPUT_ON_ERROR, но это снижет предсказуемость результата.
Сериализация доменных объектов (JsonSerializable)
В сложных бэкендах вы чаще всего будете кодировать объекты прикладного уровня (доменные объекты) для HTTP-ответов. По умолчанию json_encode() перечисляет только публичные свойства объекта:
class BlogPost {
protected int $Id = 1;
public string $Title = "Example";
}
// produces '{"Title": "Example"}'
$json = json_encode(new BlogPost());Защищённые и приватные свойства недоступны автоматически, поэтому для контроля вывода используйте интерфейс
JsonSerializable.
Интерфейс требует реализации метода
jsonSerialize(), который вызывается при сериализации экземпляра:
class BlogPost implements JsonSerializable {
protected int $Id = 1;
public string $Title = "Example";
public function jsonSerialize() {
return [
"Id" => $this->Id,
"Title" => $this->Title
];
}
}
// produces '{"Id": 1, "Title": "Example"}'
$json = json_encode(new BlogPost());jsonSerialize() может возвращать любую структуру, которую PHP умеет сериализовать в JSON — массив, объект, строку, число и т.д. Это даёт гибкость: скрывать поля, вычислять значения на лету, композиция сущностей.
Практические рекомендации и шаблоны
Важно:
- Всегда валидируйте входной JSON при приёме из ненадёжных источников.
- Используйте JSON_THROW_ON_ERROR для явной обработки ошибок и логирования.
- Для публичных API чётко документируйте схему (пример: OpenAPI/JSON Schema).
Рекомендации по флагам:
- Для человекочитаемых конфигураций используйте JSON_PRETTY_PRINT.
- Для сохранения нулевой дробной части чисел используйте JSON_PRESERVE_ZERO_FRACTION.
- Для совместимости с внешними сервисами проверяйте, нужен ли JSON_NUMERIC_CHECK — он может неожиданно превратить строки.
Шаблон сериализации доменного объекта:
class Presenter implements JsonSerializable {
private $model;
public function __construct($model) { $this->model = $model; }
public function jsonSerialize() {
return [
'id' => $this->model->getId(),
'title' => $this->model->getTitle(),
// поля, проверка прав доступа
];
}
}Когда это не работает — типичные случаи и обходные пути
- Некорректная кодировка (не UTF-8). JSON требует UTF-8; если строка содержит байты в другой кодировке, парсер вернёт ошибку. Решение: перекодировать в UTF-8.
- Большие числа (bigint) теряют точность при преобразовании в float/double. Решение: хранить как строку или использовать библиотеки для работы с большими числами.
- Циклические ссылки в объектах. json_encode вызовет ошибку. Решение: вручную преобразовать структуру в деревообразную (убрать циклы) или вернуть идентификаторы.
- Не сериализуемые значения (resource, Closure). Решение: фильтрация перед сериализацией или замена значений на допустимые представления.
Альтернативные подходы:
- Для строго типизированного обмена используйте MessagePack или protobuf (бинарные форматы) — меньше места и быстрее, но требуют схемы.
- Для валидации входящих JSON применяйте JSON Schema или библиотеки валидации.
Безопасность и приватность
- Не включайте в JSON чувствительные поля (пароли, токены) без явной необходимости. Фильтруйте данные на уровне модели/презентера.
- Логи с необработанным JSON могут раскрыть PII — маскируйте данные перед записью.
- Для GDPR/защиты данных документируйте какие поля сохраняются/передаются и по каким основаниям.
Совместимость и миграция
- Проверяйте минимальную требуемую версию PHP при использовании JSON_THROW_ON_ERROR и именованных аргументов.
- При миграции с старой версии PHP тестируйте поведение json_encode/json_decode на реальных payload’ах и в логах.
Чек-листы по ролям
Разработчик API:
- Использовать JsonSerializable для доменных объектов.
- Документировать схему ответа.
- Поддерживать JSON_PRETTY_PRINT для конфигураций.
Backend-инженер:
- Проверять кодировки (UTF-8).
- Логировать ошибки JsonException.
- Фильтровать чувствительные данные.
QA-инженер:
- Писать тесты, покрывающие ошибки парсинга.
- Проверять пограничные случаи: пустые массивы, null, большие числа.
Критерии приёмки
- Все публичные эндпоинты возвращают корректный JSON с заголовком Content-Type: application/json.
- На некорректный JSON система возвращает контролируемую ошибку (400/422) и логируется причина.
- Сериализуются все поля, требуемые спецификацией API; чувствительные поля отсутствуют.
Тестовые случаи и примеры для unit-тестов
Пример теста с PHPUnit:
public function testDecodeValidJson() {
$json = '{"foo": "bar"}';
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
$this->assertIsArray($data);
$this->assertEquals('bar', $data['foo']);
}
public function testDecodeInvalidJsonThrows() {
$this->expectException(JsonException::class);
json_decode("['malformed json", true, 512, JSON_THROW_ON_ERROR);
}Примеры кода — обработка ошибок и ответ в контроллере
try {
$payload = json_decode($request->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
return new JsonResponse(['error' => 'Invalid JSON payload'], 400);
}
// Использование презентера
$responseData = (new PostPresenter($postModel))->jsonSerialize();
return new JsonResponse($responseData);Ментальные модели и эвристики
- “Явный > неявный”: предпочитайте явную обработку ошибок (исключения), а не молчаливое возвращение null.
- “Презентер = контракт”: используйте презентер/DTO для формирования стабильного API-контракта, а не напрямую json_encode() доменной модели.
- “Флаги как конфигурация”: воспринимайте флаги json_encode/json_decode как способ конфигурировать контракт вывода.
Примеры ошибок и их разбор
Ошибка: json_decode вернул null, но JSON не содержит null. Возможные причины:
- Некорректная кодировка (не UTF-8).
- Синтаксическая ошибка в JSON.
- Превышена глубина парсинга.
Решение: логировать json_last_error_msg() или использовать JSON_THROW_ON_ERROR и перехватывать JsonException.
Часто задаваемые вопросы
Что лучше: получать массив или объект при json_decode?
Ассоциативный массив (associative=true) удобен для доступа по ключам и тестов. Объект stdClass полезен, если вы ожидаете объектную модель и не планируете модифицировать структуру.
Стоит ли всегда включать JSON_PRETTY_PRINT?
Нет — для сетевого обмена обычно предпочтительна минифицированная строка. JSON_PRETTY_PRINT полезен для конфигурационных файлов и локальной отладки.
Как хранить большие числовые идентификаторы?
Если числа могут превышать точность PHP float, храните их как строки или используйте библиотеки для работы с большими числами.
Краткое резюме
- Используйте json_decode() и json_encode() корректно — проверяйте флаги и глубину.
- Включайте JSON_THROW_ON_ERROR для явной обработки ошибок.
- Реализуйте JsonSerializable для доменных объектов, чтобы контролировать публичный контракт API.
- Фильтруйте чувствительные данные и соблюдайте требования по кодировке (UTF-8).
Важно: тестируйте поведение на реальных payload’ах и не полагайтесь на поведение по умолчанию для безопасности и согласованности API.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone