JSON в PHP: чтение, запись и надёжная обработка

Быстрые ссылки
- Чтение JSON
- Обработка ошибок парсинга
- Сериализация в JSON
- Перенос JSON в уровень доменной модели приложения
Введение
JSON — один из самых распространённых форматов сериализации данных. Он возник внутри JavaScript (JSON расшифровывается как JavaScript Object Notation) и стал стандартом для многих веб-API и конфигурационных файлов.
PHP поставляется с встроенной поддержкой JSON. Ранее функциональность реализовывалась отдельным расширением; начиная с PHP 8.0 JSON стал постоянно активным расширением, которое нельзя отключить.
Важно понимать термин: json-парсинг — процесс преобразования текстовой JSON-строки в структуры данных PHP; сериализация — обратный процесс.
Чтение JSON
Для разбора JSON используйте функцию:
json_decode()Полная сигнатура:
json_decode(string $json, bool|null $associative=null, int $depth=512, int $flags=0) : mixed;Самый простой вызов — передать JSON-строку без дополнительных аргументов. Пример строки:
{"foo": "bar"}По умолчанию она декодируется в экземпляр класса
stdClass— объект PHP с публичным свойством
fooсо значением
barЕсли нужен ассоциативный массив вместо объекта, передайте вторым аргументом
trueПример:
json_decode('{"foo":"bar"}', true);
// Результат: ["foo" => "bar"]Параметр
$depthконтролирует максимальную глубину вложенности, которую будет парсить функция. Если JSON вложен глубже, результатом будет
nullи парсинг прекращается.
Параметр
$flagsпринимает битовую маску флагов, меняющих поведение парсера (подробно в документации PHP). Флаги определяют, например, обработку больших чисел, UTF-8 и другие тонкости.
Хорошие практики при чтении JSON
- Всегда проверяйте источник JSON перед парсингом (заголовки, кодировка, размер). Внешние данные могут быть вредоносными или повреждёнными.
- Используйте строгую обработку ошибок (см. ниже) вместо слепого сравнения с null.
- Для больших JSON-файлов избегайте одновременной загрузки всей строки в память; используйте потоковый парсинг или разбивку.
Обработка ошибок парсинга
По умолчанию
json_decode()возвращает
nullпри ошибке синтаксиса. Проблема: вернувшийся null может означать как ошибочный JSON, так и корректную JSON-строку со значением null — отличить их нельзя.
Начиная с PHP 7.3, добавлен флаг
JSON_THROW_ON_ERROR. Передайте его в параметр
$flags, чтобы получать исключения при ошибках парсинга:
json_decode("['malformed json", true, 512, JSON_THROW_ON_ERROR);Это приводит к выбрасыванию объекта исключения типа
JsonExceptionпри неверном JSON. Такой подход упрощает обработку ошибок и делает поведение предсказуемым.
PHP 8 добавил именованные аргументы, что делает вызов короче:
json_decode(json: "['malformed json", flags: JSON_THROW_ON_ERROR);Эти два примера эквивалентны, но синтаксис с именованными аргументами доступен только в PHP 8 и выше.
Примеры обработки ошибок
Простой безопасный шаблон:
try {
$data = json_decode($jsonString, associative: true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
// Логируем и возвращаем пользовательскую ошибку
error_log('JSON parse error: ' . $e->getMessage());
throw new RuntimeException('Некорректный формат данных');
}Если вы используете фреймворк (Laravel, Symfony и т.п.), лучше преобразовывать JsonException в HTTP-ответ 400 с подробной, но безопасной для клиента информацией.
Когда JSON_THROW_ON_ERROR не подходит
- В задачах, где нужно попытаться восстанавливать частично повреждённые документы и продолжить обработку, может быть полезен режим без исключений вместе с JSON_PARTIAL_OUTPUT_ON_ERROR при кодировании (см. ниже).
- В средах, где исключения запрещены политикой проекта, придётся явно проверять json_last_error() после вызова json_decode без флагов.
Сериализация данных в JSON
Для преобразования PHP-значений в JSON используйте функцию:
Сигнатура:
json_encode(mixed $value, int $flags=0, int $depth=512) : string|false;PHP принимает любые значения (кроме ресурсов) и пытается корректно их отобразить в JSON. Ассоциативные массивы и объекты PHP становятся объектами JSON; скаляры мапятся напрямую.
Обратите внимание: порядок необязательных параметров отличается от json_decode() — сначала flags, затем depth.
Важные флаги при кодировании
- JSON_FORCE_OBJECT — принудительно кодирует числовые массивы как объекты JSON. Полезно, если пустой массив должен быть представлен как объект.
- JSON_PRETTY_PRINT — добавляет переносы строк и отступы для удобочитаемости (конфигурационные файлы, примеры). Не используйте для сетевого трафика, если важна экономия трафика.
- JSON_PRESERVE_ZERO_FRACTION — сохраняет дробную часть у чисел вида 0.0, вместо сокращения до 0.
- JSON_NUMERIC_CHECK — пытается преобразовать строковые числа в числовые значения в выходном JSON (быть осторожным — это глобальная трансформация строк).
- JSON_PARTIAL_OUTPUT_ON_ERROR — пытается продолжить сериализацию при ошибках, подставляя допустимые значения.
Пример:
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_PRESERVE_ZERO_FRACTION);Особенности и подводные камни при кодировании
- Ресурсы PHP не сериализуются; json_encode вернёт false. Всегда проверяйте результат и обрабатывайте ошибки (JSON_THROW_ON_ERROR недоступен на момент кодирования в старых версиях PHP; в современных версиях можно использовать JsonException при кодировании, если включён соответствующий флаг).
- Порядок ключей в ассоциативном массиве будет сохранён при кодировании, но JSON вроде не гарантирует семантику порядка; не полагайтесь на порядок в клиентских кодах.
- JSON_NUMERIC_CHECK может неожиданно преобразовать строки, которые вы хотели сохранить как строки (например, почтовые индексы с ведущими нулями).
Перенос JSON в доменный слой приложения
В бэкендах обычно приходится сериализовать объекты доменной модели (например, BlogPost, User) в JSON для HTTP-ответов. По умолчанию json_encode обходит только публичные свойства класса.
Пример простого класса:
class BlogPost {
protected int $Id = 1;
public string $Title = "Example";
}
// produces '{"Title": "Example"}'
$json = json_encode(new BlogPost());Проблема: защищённые и приватные свойства не попадут в результат. Чтобы контролировать сериализацию, реализуйте интерфейс
JsonSerializableи определите метод
jsonSerialize(), который возвращает любые данные, пригодные для сериализации в JSON (массив, объект, скаляр).
Пример:
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 сможет рекурсивно сериализовать — ещё один объект, массив и т.д.
Альтернативы и подходы
- DTO/ресурсные объекты: вместо того чтобы заставлять доменные объекты знать о формате JSON, создавайте промежуточные объекты (Data Transfer Objects), которые ответственны за представление данных.
- Сериализаторы/нормализаторы (Symfony Serializer, Laravel API Resources): библиотеки дают гибкий контроль над группами полей, версионированием и конфиденциальными полями.
Практические рекомендации по безопасности и валидации
- Никогда не доверяйте JSON, полученному извне. Валидируйте структуру и типы до использования в критичных операциях (запросы к БД, выполнение команд).
- При возвращении ошибок клиенту не раскрывайте внутренние детали: логируйте стек ошибки на сервере, а клиенту отдавайте обобщённое сообщение.
- Ограничьте максимальный размер тела запроса и глубину вложенности для защиты от атак, направленных на исчерпание памяти.
Производительность и масштабирование
- Для больших JSON-объектов используйте потоковый парсинг (событийные парсеры) или разбивайте данные на чанки; стандартный json_decode требует полной загрузки строки в память.
- Для повторного использования структур данных кэшируйте результирующие объекты/JSON, если данные неизменны.
- Профилируйте места сериализации: частые вызовы json_encode на больших объектах могут стать узким местом.
Совместимость и миграция между версиями PHP
- PHP 7.3 ввёл JSON_THROW_ON_ERROR.
- PHP 8.0 сделал расширение JSON всегда активным и добавил именованные аргументы, упрощающие вызовы.
Если ваше приложение поддерживает разные версии PHP, оберните использование новых флагов проверкой версии или адаптером, который использует совместимый режим в старых версиях.
Шаблон принятия (Критерии приёмки)
- JSON от клиента парсится без исключений при корректных данных.
- При неверном JSON сервер возвратит корректный обработанный ответ с кодом 400 (или внутренней бизнес-ошибкой по соглашению).
- Логи содержат подробную техническую информацию, но клиент получает безопасное сообщение.
- Сериализованные объекты не раскрывают приватные поля, которые не предназначены для внешнего употребления.
Чек-листы по ролям
Разработчик:
- Использую json_decode с JSON_THROW_ON_ERROR в новых проектах.
- Проверяю размер и глубину входящего JSON.
- Логирую детальную ошибку при исключении.
Архитектор:
- Решил, где используются DTO и где JsonSerializable уместен.
- Определил стратегии версионирования API и формат ответов.
Операции/DevOps:
- Ограничил размер тела запросов на уровне веб-сервера/платформы.
- Настроил мониторинг ошибок JsonException и частоты отказов парсинга.
Примеры тестов и критерии приёмки
Тесты парсинга:
- При корректном JSON возвращается ожидаемая структура и типы.
- При неверном JSON выбрасывается JsonException и логируется ошибка.
Тесты сериализации:
- JsonSerializable возвращает только ожидаемые ключи.
- JSON_PRESERVE_ZERO_FRACTION сохраняет дробную часть у 0.0.
Маленькая методология миграции — шаги
- Добавьте глобальный адаптер для работы с JSON (оборачивает json_decode/json_encode).
- Внедрите JSON_THROW_ON_ERROR внутри адаптера с обратной совместимостью для старых PHP.
- Мигрируйте критичные места на использование адаптера; покрывайте тестами.
- Анализируйте логирование ошибок парсинга и корректируйте обработку.
Частые ошибки и когда это не сработает
- Ожидание, что json_encode автоматически превратит приватные свойства: не сработает — нужен JsonSerializable или DTO.
- Использование JSON_NUMERIC_CHECK без анализа входных данных — может испортить строковые идентификаторы.
- Слепое доверие размера и глубины JSON — приводит к DoS через переполнение памяти.
Малая галерея крайних случаев
- Пустая строка как вход — json_decode вернёт null; с JSON_THROW_ON_ERROR бросит исключение.
- Большие числа, не помещающиеся в 64-бит — стоит хранить как строки и обрабатывать на клиенте/сервере явно.
Мини‑шпаргалка (cheat sheet)
- Чтение: json_decode($json, true, 512, JSON_THROW_ON_ERROR)
- Запись: json_encode($value, JSON_PRETTY_PRINT | JSON_PRESERVE_ZERO_FRACTION)
- Кастомная сериализация: implements JsonSerializable + jsonSerialize()
- Проверка ошибок (старые версии): json_last_error(), json_last_error_msg()
Пример принятого рабочего потока (Playbook)
- При получении тела запроса валидируем заголовок Content-Type: application/json.
- Ограничиваем длину тела на уровне NGINX / web-сервера.
- Парсим через адаптер с JSON_THROW_ON_ERROR.
- Валидируем структуру через схемы (например, JSON Schema) или DTO-валидаторы.
- При ошибке отправляем 400 и логируем детально на сервере.
Небольшая «1‑строчная» глоссарная справка
- json_decode: парсит JSON в PHP-структуры.
- json_encode: сериализует PHP-значения в JSON-строку.
- JsonSerializable: интерфейс для контроля сериализации объектов.
- JSON_THROW_ON_ERROR: флаг, заставляющий выбрасывать исключение при ошибках парсинга.
FAQ
Q: Что делать, если json_decode возвращает null без исключения?
A: Проверьте json_last_error() и json_last_error_msg(), либо включите JSON_THROW_ON_ERROR для получения исключения и ясного поведения.
Q: Как сериализовать приватные свойства объекта в JSON?
A: Реализуйте JsonSerializable и возвращайте массив/объект с нужными полями либо используйте DTO.
Q: Можно ли использовать json_encode для потоковой записи больших наборов данных?
A: json_encode не потоковая. Для больших наборов лучше формировать JSON по частям вручную или использовать библиотеку/стриминг.
Заключение
JSON в PHP — простой и мощный инструмент, но при неправильном использовании он даёт неожиданные ошибки: потеря данных, некорректная сериализация, уязвимости по памяти. Применяйте JSON_THROW_ON_ERROR для детальной обработки ошибок, используйте JsonSerializable или DTO для контроля вывода, валидируйте входные данные и учитывайте производительность при работе с большими объёмами.
Внедрите адаптеры и стандарты в вашем проекте — это снизит технический долг и упростит миграцию между версиями PHP.
Важно: логируйте технические детали, но не раскрывайте их клиентам; на клиент отдавайте безопасные сообщения об ошибках.
Похожие материалы
Несколько аккаунтов Skype: Multi Skype Launcher
Журнал для работы: повысить продуктивность
Персональные звуки уведомлений на Android
Скачивание шоу Hulu для офлайн‑просмотра
Microsoft Start: персонализированная новостная лента