一、基本概念¶
1. json_decode
的基本用法¶
$json = '{"name": "John", "age": 30}';
$data = json_decode($json);
// $data 是一个 stdClass 对象
2. 将 JSON 解码为关联数组¶
$data = json_decode($json, true);
// $data 是一个关联数组
然而,json_decode
并不直接支持将 JSON 数据解码为特定的自定义类实例。为了实现这一点,需要手动映射数据或使用辅助函数。
二、自定义类映射¶
假设有以下 JSON 数据,其中包含一个用户及其多个地址:
{
"name": "John Doe",
"age": 30,
"addresses": [
{
"street": "123 Main St",
"city": "New York",
"country": "USA"
},
{
"street": "456 High St",
"city": "London",
"country": "UK"
}
]
}
1. 定义 PHP 类¶
首先,定义与 JSON 结构相对应的 PHP 类。
<?php
class Address {
public string $street;
public string $city;
public string $country;
public function __construct(string $street, string $city, string $country) {
$this->street = $street;
$this->city = $city;
$this->country = $country;
}
}
class User {
public string $name;
public int $age;
/** @var Address[] */
public array $addresses;
public function __construct(string $name, int $age, array $addresses) {
$this->name = $name;
$this->age = $age;
$this->addresses = $addresses;
}
}
2. 创建映射函数¶
编写一个递归函数,将 stdClass
对象转换为特定的类实例。
<?php
function mapJsonToClass(object $data, string $className) {
if (!class_exists($className)) {
throw new Exception("Class $className does not exist.");
}
$reflectionClass = new ReflectionClass($className);
$constructor = $reflectionClass->getConstructor();
$parameters = $constructor->getParameters();
$args = [];
foreach ($parameters as $param) {
$paramName = $param->getName();
$paramType = $param->getType();
if ($paramType === null) {
throw new Exception("Parameter $paramName in class $className has no type hint.");
}
$typeName = $paramType->getName();
if (class_exists($typeName)) {
if (is_array($data->$paramName)) {
// 假设数组元素类名为单数形式,例如 addresses 对应 Address
$itemClass = rtrim($typeName, 's'); // 简单的复数转单数
$items = [];
foreach ($data->$paramName as $item) {
$items[] = mapJsonToClass($item, $itemClass);
}
$args[] = $items;
} else {
$args[] = mapJsonToClass($data->$paramName, $typeName);
}
} else {
// 基本类型
$args[] = $data->$paramName;
}
}
return $reflectionClass->newInstanceArgs($args);
}
注意: 上述函数假设类的构造函数参数顺序与 JSON 属性顺序一致,并且复数形式的属性名对应单数形式的类名(例如 addresses
对应 Address
)。在实际应用中,可能需要更复杂的逻辑来处理不同的命名约定和类型映射。
3. 使用映射函数¶
<?php
$json = '{
"name": "John Doe",
"age": 30,
"addresses": [
{
"street": "123 Main St",
"city": "New York",
"country": "USA"
},
{
"street": "456 High St",
"city": "London",
"country": "UK"
}
]
}';
$data = json_decode($json);
$user = mapJsonToClass($data, 'User');
print_r($user);
输出结果:
User Object
(
[name] => John Doe
[age] => 30
[addresses] => Array
(
[0] => Address Object
(
[street] => 123 Main St
[city] => New York
[country] => USA
)
[1] => Address Object
(
[street] => 456 High St
[city] => London
[country] => UK
)
)
)
三、处理多级嵌套¶
假设 JSON 数据更复杂,具有多级嵌套,例如用户包含多个订单,每个订单包含多个商品:
{
"name": "John Doe",
"age": 30,
"addresses": [
{
"street": "123 Main St",
"city": "New York",
"country": "USA"
}
],
"orders": [
{
"orderId": 1001,
"products": [
{"productId": "A1", "name": "Product A"},
{"productId": "B2", "name": "Product B"}
]
},
{
"orderId": 1002,
"products": [
{"productId": "C3", "name": "Product C"}
]
}
]
}
1. 定义额外的 PHP 类¶
<?php
class Product {
public string $productId;
public string $name;
public function __construct(string $productId, string $name) {
$this->productId = $productId;
$this->name = $name;
}
}
class Order {
public int $orderId;
/** @var Product[] */
public array $products;
public function __construct(int $orderId, array $products) {
$this->orderId = $orderId;
$this->products = $products;
}
}
class User {
public string $name;
public int $age;
/** @var Address[] */
public array $addresses;
/** @var Order[] */
public array $orders;
public function __construct(string $name, int $age, array $addresses, array $orders) {
$this->name = $name;
$this->age = $age;
$this->addresses = $addresses;
$this->orders = $orders;
}
}
2. 更新映射函数以处理多级嵌套¶
现有的 mapJsonToClass
函数已经是递归的,可以处理多级嵌套。然而,为了更健壮地处理不同的命名约定,可以对其进行改进。例如,可以使用注释或属性来指定数组元素的类名。
以下是一个改进版的映射函数,使用 PHP 8 的属性来指定类的类型(需要 PHP 8.0+):
1. 使用属性指定类型¶
<?php
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
class JsonType {
public string $type;
public function __construct(string $type) {
$this->type = $type;
}
}
2. 更新类定义以使用属性¶
<?php
class Address {
public string $street;
public string $city;
public string $country;
public function __construct(string $street, string $city, string $country) {
$this->street = $street;
$this->city = $city;
$this->country = $country;
}
}
class Product {
public string $productId;
public string $name;
public function __construct(string $productId, string $name) {
$this->productId = $productId;
$this->name = $name;
}
}
class Order {
public int $orderId;
#[JsonType('Product')]
public array $products;
public function __construct(int $orderId, array $products) {
$this->orderId = $orderId;
$this->products = $products;
}
}
class User {
public string $name;
public int $age;
#[JsonType('Address')]
public array $addresses;
#[JsonType('Order')]
public array $orders;
public function __construct(string $name, int $age, array $addresses, array $orders) {
$this->name = $name;
$this->age = $age;
$this->addresses = $addresses;
$this->orders = $orders;
}
}
3. 更新映射函数以使用属性信息¶
<?php
function mapJsonToClassWithAttributes(object $data, string $className) {
if (!class_exists($className)) {
throw new Exception("Class $className does not exist.");
}
$reflectionClass = new ReflectionClass($className);
$constructor = $reflectionClass->getConstructor();
$parameters = $constructor->getParameters();
$args = [];
foreach ($parameters as $param) {
$paramName = $param->getName();
$paramType = $param->getType();
if ($paramType === null) {
throw new Exception("Parameter $paramName in class $className has no type hint.");
}
$typeName = $paramType->getName();
// 获取属性反射
$property = $reflectionClass->getProperty($paramName);
$attributes = $property->getAttributes(JsonType::class);
$itemClass = null;
if (!empty($attributes)) {
// 假设只有一个 JsonType 属性
$jsonType = $attributes[0]->newInstance();
$itemClass = $jsonType->type;
}
if (class_exists($typeName)) {
if (is_array($data->$paramName)) {
if ($itemClass) {
$items = [];
foreach ($data->$paramName as $item) {
$items[] = mapJsonToClassWithAttributes($item, $itemClass);
}
$args[] = $items;
} else {
$args[] = $data->$paramName;
}
} else {
$args[] = mapJsonToClassWithAttributes($data->$paramName, $typeName);
}
} else {
// 基本类型
$args[] = $data->$paramName;
}
}
return $reflectionClass->newInstanceArgs($args);
}
4. 使用改进后的映射函数¶
<?php
$json = '{
"name": "John Doe",
"age": 30,
"addresses": [
{
"street": "123 Main St",
"city": "New York",
"country": "USA"
}
],
"orders": [
{
"orderId": 1001,
"products": [
{"productId": "A1", "name": "Product A"},
{"productId": "B2", "name": "Product B"}
]
},
{
"orderId": 1002,
"products": [
{"productId": "C3", "name": "Product C"}
]
}
]
}';
$data = json_decode($json);
$user = mapJsonToClassWithAttributes($data, 'User');
print_r($user);
输出结果:
User Object
(
[name] => John Doe
[age] => 30
[addresses] => Array
(
[0] => Address Object
(
[street] => 123 Main St
[city] => New York
[country] => USA
)
)
[orders] => Array
(
[0] => Order Object
(
[orderId] => 1001
[products] => Array
(
[0] => Product Object
(
[productId] => A1
[name] => Product A
)
[1] => Product Object
(
[productId] => B2
[name] => Product B
)
)
)
[1] => Order Object
(
[orderId] => 1002
[products] => Array
(
[0] => Product Object
(
[productId] => C3
[name] => Product C
)
)
)
)
)
四、使用现有库简化映射¶
手动编写映射函数在处理复杂或多变的 JSON 结构时可能会变得繁琐。可以使用现有的库来简化这一过程。以下是几个常用的 PHP 库:
1. JMS Serializer¶
JMS Serializer 是一个功能强大的库,支持复杂的序列化和反序列化,包括 JSON 到 PHP 对象的映射。
安装:
composer require jms/serializer
使用示例:
<?php
require 'vendor/autoload.php';
use JMSSerializerSerializerBuilder;
class Address {
public string $street;
public string $city;
public string $country;
}
class Product {
public string $productId;
public string $name;
}
class Order {
public int $orderId;
/** @var Product[] */
public array $products;
}
class User {
public string $name;
public int $age;
/** @var Address[] */
public array $addresses;
/** @var Order[] */
public array $orders;
}
$json = '{
"name": "John Doe",
"age": 30,
"addresses": [
{
"street": "123 Main St",
"city": "New York",
"country": "USA"
}
],
"orders": [
{
"orderId": 1001,
"products": [
{"productId": "A1", "name": "Product A"},
{"productId": "B2", "name": "Product B"}
]
}
]
}';
$serializer = SerializerBuilder::create()->build();
$user = $serializer->deserialize($json, User::class, 'json');
print_r($user);
优点:
支持复杂的映射和注释。
支持循环引用和多态类型。
丰富的配置选项。
缺点:
需要学习和理解其配置和注解方式。
增加项目的依赖。
2. Spatie Data Transfer Object (DTO)¶
Spatie DTO 是一个轻量级的库,用于将数组或 JSON 数据映射到 PHP 对象。
安装:
composer require spatie/data-transfer-object
使用示例:
<?php
require 'vendor/autoload.php';
use SpatieDataTransferObjectDataTransferObject;
class AddressDTO extends DataTransferObject {
public string $street;
public string $city;
public string $country;
}
class ProductDTO extends DataTransferObject {
public string $productId;
public string $name;
}
class OrderDTO extends DataTransferObject {
public int $orderId;
/** @var ProductDTO[] */
public array $products;
}
class UserDTO extends DataTransferObject {
public string $name;
public int $age;
/** @var AddressDTO[] */
public array $addresses;
/** @var OrderDTO[] */
public array $orders;
}
$json = '{
"name": "John Doe",
"age": 30,
"addresses": [
{
"street": "123 Main St",
"city": "New York",
"country": "USA"
}
],
"orders": [
{
"orderId": 1001,
"products": [
{"productId": "A1", "name": "Product A"},
{"productId": "B2", "name": "Product B"}
]
}
]
}';
$data = json_decode($json, true);
$user = new UserDTO($data);
print_r($user);
优点:
简单易用,适合基本的映射需求。
自动类型转换和验证。
缺点:
功能不如 JMS Serializer 丰富。
需要定义 DTO 类。
五、总结¶
将 JSON 数据映射到特定的 PHP 类涉及以下步骤:
定义 PHP 类:根据 JSON 结构定义相应的 PHP 类,确保属性和类型与 JSON 数据匹配。
编写映射逻辑:使用递归函数或现有库,将
stdClass
对象或关联数组转换为特定的类实例。处理嵌套结构:确保映射函数能够递归处理多级嵌套的数组和对象。
使用库简化过程:对于复杂的映射需求,考虑使用现有的序列化/反序列化库,如 JMS Serializer 或 Spatie DTO。