Типы данных JSON в PHP: от простого к сложному
Основы работы с типами данных JSON в PHP
JSON (JavaScript Object Notation) поддерживает шесть типов данных: строку (string), число (number), объект (object), массив (array), логическое значение (boolean) и null. В PHP этим типам соответствуют строки, целые и вещественные числа, ассоциативные массивы и объекты, индексированные массивы, булевы значения и NULL. Для преобразования между PHP и JSON используются функции json_encode() и json_decode(). Ниже рассмотрены эффективные приёмы и альтернативные подходы.
Основное решение: стандартное кодирование и декодирование
Самый надёжный способ - использовать встроенные функции PHP с флагами, контролирующими поведение.
$data = [
'name' => 'Иван',
'age' => 30,
'is_active' => true,
'tags' => ['php', 'json'],
'metadata' => null
];
// Кодирование с флагами: экранирование Unicode, pretty print (для отладки)
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
echo $json;
{
"name": "Иван",
"age": 30,
"is_active": true,
"tags": ["php", "json"],
"metadata": null
}
Декодирование с преобразованием в ассоциативный массив (второй параметр true):
$decoded = json_decode($json, true);
var_dump($decoded);
array(5) {
["name"]=> string(8) "Иван"
["age"]=> int(30)
["is_active"]=> bool(true)
["tags"]=> array(2) {
[0]=> string(3) "php"
[1]=> string(4) "json"
}
["metadata"]=> NULL
}
При ошибках кодирования/декодирования следует проверять json_last_error() и json_last_error_msg().
Как закодировать объект произвольного класса?
Реализовать интерфейс JsonSerializable, который требует метод jsonSerialize().
class User implements JsonSerializable {
public $name;
protected $password;
public function __construct($name, $password) {
$this->name = $name;
$this->password = $password;
}
public function jsonSerialize(): mixed {
return [
'name' => $this->name,
// пароль не включаем
];
}
}
$user = new User('Алексей', 'secret123');
echo json_encode($user, JSON_UNESCAPED_UNICODE);
{"name":"Алексей"}
Проблема: если забыть реализовать JsonSerializable, json_encode вернёт пустой объект или false для protected/private свойств.
Решение: всегда явно реализовывать интерфейс или делать нужные свойства public.
Как проверить, является ли строка валидным JSON, не вызывая исключение?
В PHP 8.3 появилась функция json_validate(). Раньше использовали json_decode() с проверкой ошибки.
$jsonString = '{"key": "value"}';
// Способ 1 (PHP 8.3+)
if (json_validate($jsonString)) {
echo 'Валидный JSON';
}
// Способ 2 (универсальный)
$decoded = json_decode($jsonString);
if (json_last_error() === JSON_ERROR_NONE) {
echo 'Валидный JSON';
}
Проблема: json_validate не сообщает о причине ошибки. Для деталей всё равно нужен json_last_error.
Как декодировать JSON в объект конкретного класса?
Использовать json_decode() с флагом JSON_OBJECT_AS_ARRAY (по умолчанию false) и затем привести к нужному классу через ручное заполнение или сторонние библиотеки. В PHP нет встроенного гидратора.
class Product {
public string $title;
public float $price;
}
$json = '{"title":"Книга","price":499.50}';
$stdObj = json_decode($json); // объект stdClass
$product = new Product();
$product->title = $stdObj->title;
$product->price = $stdObj->price;
Проблема: при больших наборах данных ручное присваивание неудобно. Используют сторонние библиотеки (Symfony Serializer, JMS Serializer).
Как обработать большие целые числа, не теряя точность?
По умолчанию json_decode преобразует большие числа в float, что может потерять точность. Флаг JSON_BIGINT_AS_STRING оставляет числа строками.
$bigJson = '{"id": 12345678901234567890}';
$decoded = json_decode($bigJson, false, 512, JSON_BIGINT_AS_STRING);
var_dump($decoded->id); // string(20) "12345678901234567890"
Ошибка: если забыть флаг, число будет преобразовано во float с потерей знаков.
Как кодировать данные с кириллицей без экранирования?
Флаг JSON_UNESCAPED_UNICODE отключает экранирование символов Unicode.
echo json_encode(['привет'], JSON_UNESCAPED_UNICODE);
// ["привет"]
Без флага результат: ["\u043f\u0440\u0438\u0432\u0435\u0442"]
Как принудительно закодировать пустой массив как объект?
Флаг JSON_FORCE_OBJECT - если массив пуст, он станет пустым объектом. Для непустых массивов поведение не меняется.
echo json_encode([], JSON_FORCE_OBJECT); // {}
echo json_encode(['a'], JSON_FORCE_OBJECT); // ["a"]
Как игнорировать ошибки циклических ссылок?
По умолчанию json_encode возвращает false, если встречает рекурсивные объекты. Флаг JSON_PARTIAL_OUTPUT_ON_ERROR позволяет кодировать то, что возможно, заменяя проблемные части на null.
$obj = new stdClass();
$obj->self = $obj;
echo json_encode($obj, JSON_PARTIAL_OUTPUT_ON_ERROR);
// {"self":null}
Проблема: потеря данных - не всегда желаемое поведение. Рекомендуется избегать циклических ссылок в данных для JSON.
Типичные ошибки и их решения
Ошибка: json_encode возвращает false при кодировании ресурсов (например, потока).
Решение: преобразовать ресурс в строку или массив данных до кодирования.
Ошибка: json_decode возвращает null для строки 'null'.
Решение: отличить настоящий null от ошибки парсинга можно через проверку json_last_error().
Ошибка: проблема с глубиной вложенности (по умолчанию 512 уровней, параметр depth).
Решение: увеличить depth в вызове json_decode или json_encode.
Расширенные примеры работы с типами данных JSON
Набор демонстраций, охватывающий нестандартные ситуации и комбинации флагов.
1. Кодирование ассоциативного массива с числовыми ключами
$arr = [0 => 'a', 1 => 'b', '2' => 'c']; // ключи-строки, содержащие числа, тоже считаются числовыми
echo json_encode($arr); // ["a","b","c"]
$mixed = ['foo' => 'bar', 0 => 'baz'];
echo json_encode($mixed); // {"foo":"bar","0":"baz"} - нечисловой ключ превращает в объект
2. Декодирование с сохранением типа float
$json = '{"value": 1.5e10}';
$decoded = json_decode($json);
var_dump($decoded->value); // float(15000000000)
// Принудительно как строка:
$decoded2 = json_decode($json, false, 512, JSON_BIGINT_AS_STRING);
// но JSON_BIGINT_AS_STRING применяется только к целым, не к float
3. Сериализация объекта DateTime
$date = new DateTime('2025-03-15 14:30:00');
echo json_encode($date); // {"date":"2025-03-15 14:30:00.000000","timezone_type":3,"timezone":"UTC"}
// Лучше предварительно форматировать:
echo json_encode(['created' => $date->format('c')], JSON_UNESCAPED_UNICODE);
// {"created":"2025-03-15T14:30:00+00:00"}
4. Кодирование вложенных объектов с приватными свойствами через JsonSerializable
class Address implements JsonSerializable {
private string $city;
private string $zip;
public function __construct($city, $zip) {
$this->city = $city;
$this->zip = $zip;
}
public function jsonSerialize(): mixed {
return ['city' => $this->city, 'zip' => $this->zip];
}
}
class Person implements JsonSerializable {
private string $name;
private Address $address;
public function __construct($name, Address $address) {
$this->name = $name;
$this->address = $address;
}
public function jsonSerialize(): mixed {
return ['name' => $this->name, 'address' => $this->address];
}
}
$person = new Person('Елена', new Address('Москва', '101000'));
echo json_encode($person, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
{
"name": "Елена",
"address": {
"city": "Москва",
"zip": "101000"
}
}
5. Обработка ошибки при декодировании повреждённого JSON
$badJson = '{"key": "value"';
$result = json_decode($badJson);
if (json_last_error() !== JSON_ERROR_NONE) {
echo 'Ошибка: ' . json_last_error_msg(); // Syntax error, malformed JSON
}
6. Использование JSON_NUMERIC_CHECK для преобразования строковых чисел в числа
$json = '{"id":"123","price":"45.67"}';
$decoded = json_decode($json, true, 512, JSON_NUMERIC_CHECK);
var_dump($decoded['id']); // int(123)
var_dump($decoded['price']); // float(45.67)
// Осторожно: строки вида '123abc' останутся строками
7. Кодирование с флагом JSON_INVALID_UTF8_IGNORE / JSON_INVALID_UTF8_SUBSTITUTE
$data = ['name' => "\xB1\x31"]; // некорректный UTF-8
$json = json_encode($data, JSON_INVALID_UTF8_SUBSTITUTE);
echo $json; // {"name":"\ufffd1"} - заменено на символ замены
// Если использовать JSON_INVALID_UTF8_IGNORE - строка будет пропущена
8. Декодирование JSON-массива объектов с преобразованием в класс (ручной гидратор)
$json = '[{"id":1,"name":"A"},{"id":2,"name":"B"}]';
$items = json_decode($json); // массив stdClass
$objects = array_map(function($std) {
$obj = new stdClass();
$obj->id = $std->id;
$obj->name = $std->name;
// здесь можно создать инстанс нужного класса
return $obj;
}, $items);
var_dump($objects);
9. Пакетная проверка валидности нескольких JSON-строк
$strings = ['valid', '{"key":123}', 'not json', '[]'];
foreach ($strings as $str) {
$valid = function_exists('json_validate') ? json_validate($str) : (json_decode($str) !== null || json_last_error() === JSON_ERROR_NONE);
echo "$str -> " . ($valid ? 'OK' : 'FAIL') . "\n";
}
valid -> FAIL
{"key":123} -> OK
not json -> FAIL
[] -> OK
10. Кодирование многомерного ассоциативного массива с индексированными подмассивами
$data = [
'products' => [
['id' => 1, 'tags' => ['new', 'sale']],
['id' => 2, 'tags' => ['popular']]
],
'meta' => ['count' => 2]
];
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
{
"products": [
{
"id": 1,
"tags": ["new", "sale"]
},
{
"id": 2,
"tags": ["popular"]
}
],
"meta": {
"count": 2
}
}
Эти примеры охватывают практические сценарии, с которыми сталкиваются разработчики при работе с JSON в PHP. Всегда полезно проверять версию PHP и доступные флаги, так как в новых версиях появляются дополнительные возможности (например, json_validate, JSON_THROW_ON_ERROR).