brick / json-mapper
将 JSON 数据映射到强类型 PHP DTO
Requires
- php: ^8.1
- brick/reflection: ~0.5.0
Requires (Dev)
- php-coveralls/php-coveralls: ^2.5
- phpunit/phpunit: ^10.1.0
- vimeo/psalm: 5.6.0
README
将 JSON 数据映射到强类型 PHP DTO
简介
此库提供了一个易于使用、安全且强大的方式来将 JSON 数据映射到强类型 PHP 对象。
它读取在类构造函数上定义的参数类型和注解,以将 JSON 数据映射到您的 DTO,并且可以零配置工作。
安装
此库可以通过 Composer 安装。
composer require brick/json-mapper
要求
此库需要 PHP 8.1 或更高版本。
项目状态和发布流程
尽管此库仍在开发中,但它经过充分测试,被认为足够稳定,可以在生产环境中使用。
当前版本为 0.x.y
。当引入非破坏性更改(添加新方法、优化现有代码等)时,y
增量。
当引入破坏性更改时,始终启动一个新的 0.x
版本周期。
因此,可以将项目锁定到给定的版本周期,例如 0.1.*
。
如果您需要升级到较新的版本周期,请查看 发布历史 以获取每个后续 0.x.0
版本引入的更改列表。
用法
基本用法
JsonMapper
提供了一个名为 map()
的单方法,它接受一个 JSON 字符串和一个类名,并返回给定类的实例。
use Brick\JsonMapper\JsonMapper; class User { public function __construct( public int $id, public string $name, ) { } } $json = '{ "id": 123, "name": "John Doe" }'; $mapper = new JsonMapper(); $user = $mapper->map($json, User::class); echo $user->name; // John Doe
嵌套对象
JsonMapper
将读取参数类型和注解以映射嵌套对象
class Album { public function __construct( public int $id, public string $title, public Artist $artist, ) { } } class Artist { public function __construct( public int $id, public string $name, ) { } } $json = '{ "id": 456, "title": "The Wall", "artist": { "id": 789, "name": "Pink Floyd" } }'; $mapper = new JsonMapper(); $album = $mapper->map($json, Album::class); echo $album->artist->name; // Pink Floyd
数组
可以使用 @param
注解记录数组,这些注解将被解析并用于映射 JSON 数据
class Customer { /** * @param Address[] $addresses */ public function __construct( public int $id, public string $name, public array $addresses, ) { } } class Address { public function __construct( public string $street, public string $city, ) { } } $json = '{ "id": 123, "name": "John Doe", "addresses": [ { "street": "123 Main Street", "city": "New York" }, { "street": "456 Side Street", "city": "New York" } ] }'; $mapper = new JsonMapper(); $customer = $mapper->map($json, Customer::class); foreach ($customer->addresses as $address) { var_export($address instanceof Address); // true }
联合类型
如果参数被声明为可能的类型联合,则 JsonMapper
将自动尝试将 JSON 数据映射到正确的类型
class Order { public function __construct( public readonly int $id, public readonly string $amount, public readonly Person|Company $customer, // union type ) { } } class Person { public function __construct( public readonly int $id, public readonly string $firstname, public readonly string $lastname, ) { } } class Company { public function __construct( public readonly int $id, public readonly string $name, public readonly string $companyNumber, ) { } } $json = '{ "id": 1, "amount": "24.99", "customer": { "id": 2, "firstname": "John", "lastname": "Doe" } }'; $mapper = new JsonMapper(); $order = $mapper->map($json, Order::class); // JsonMapper automatically determined that the "id", "firstname", // and "lastname" properties correspond to a Person and not a Company. var_export($order->customer instanceof Person); // true
为此,JsonMapper
尝试将 JSON 对象映射到联合中可能的所有 PHP 类。如果没有类匹配,或者有多个类匹配,则抛出异常。
复杂联合
JsonMapper
可以解析、映射和验证任何可能嵌套的类型组合
/** * @param (Person|Company|(string|int)[])[]|null $customers */ public function __construct( public readonly ?array $customers, ) { }
这目前有两个限制
-
您必须使用
Type[]
语法,而不是array
语法; -
联合中不能使用多个数组类型;例如,这是允许的
/** * @param (Person|Company)[] $value */
但不允许这种情况
/** * @param Person[]|Company[] $value */
枚举
JsonMapper
可以将 JSON 字符串和整数映射到有后盾的枚举
class Order { public function __construct( public readonly int $id, public readonly OrderStatus $status, ) { } } enum OrderStatus: string { case PENDING = 'pending'; case SHIPPED = 'shipped'; case DELIVERED = 'delivered'; } $json = '{ "id": 1, "status": "shipped" }'; $mapper = new JsonMapper(); $order = $mapper->map($json, Order::class); var_export($order->status === OrderStatus::SHIPPED); // true
非后盾枚举,即没有 string
或 int
值的枚举,出于故意不支持。
严格性
该库具有非常严格的默认值(其中一些可以通过配置覆盖),如果 JSON 数据与 DTO 的构造函数签名不精确匹配,或者 DTO 包含无效或不受支持的 @param
注解,则将抛出异常。
参数类型必须完全匹配,与 PHP 的 strict_types
具有相同的语义。
JsonMapper
保证每个构造函数参数,即使是使用 @param
软类型声明的,都将传递与声明类型兼容的值。 结果是您可以100%信任的 DTO。
选项
JsonMapper
构造函数接受以下选项
-
$allowUntypedArrays
默认情况下,如果参数被声明为
array
而没有对应的@param
注解,或者仅仅被文档说明为@param array
,则JsonMapper
将会抛出异常。将此选项设置为
true
,则JsonMapper
将允许此类参数,并接受将 JSON 数组原样传递,而不检查或映射其内容$mapper = new JsonMapper( allowUntypedArrays: true, );
-
$allowUntypedObjects
默认情况下,如果参数被声明为
object
或stdClass
,则JsonMapper
将会抛出异常。将此选项设置为
true
,则JsonMapper
将允许此类参数,并接受将 JSON 对象作为stdClass
实例传递,而不检查或映射其内容$mapper = new JsonMapper( allowUntypedObjects: true, );
-
$allowMixed
默认情况下,如果参数被声明为
mixed
,则JsonMapper
将会抛出异常。将此选项设置为
true
,则JsonMapper
将允许此类参数,并接受将 JSON 值原样传递,而不检查或映射其内容$mapper = new JsonMapper( allowMixed: true, );
-
$onExtraProperties
此选项接受一个
OnExtraProperties
枚举值,并控制当 JSON 对象包含在相应 DTO 构造函数签名中未匹配的属性时,JsonMapper
的反应方式-
OnExtraProperties::THROW_EXCEPTION
JsonMapper
将抛出JsonMapperException
。这是默认值。 -
OnExtraProperties::IGNORE
JsonMapper
将忽略任何额外属性use Brick\JsonMapper\JsonMapper; use Brick\JsonMapper\OnExtraProperties; class Order { public function __construct( public readonly int $id, public readonly string $amount, ) { } } $json = '{ "id": 1, "amount": "100.00", "extraProperty": "foo", "otherExtraProperty": "bar" }'; $mapper = new JsonMapper( onExtraProperties: OnExtraProperties::IGNORE, ); // extra properties "extraProperty" and "otherExtraProperty" are ignored, // and do not throw an exception anymore. $order = $mapper->map($json, Order::class);
-
-
$onMissingProperties
此选项接受一个
OnMissingProperties
枚举值,并控制当 JSON 对象缺少在相应 DTO 构造函数签名中声明的属性时,JsonMapper
的反应方式-
OnMissingProperties::THROW_EXCEPTION
JsonMapper
将抛出JsonMapperException
。这是默认值。 -
OnMissingProperties::SET_NULL
如果 JSON 属性缺失且参数是可空的,则
JsonMapper
将参数设置为null
use Brick\JsonMapper\JsonMapper; use Brick\JsonMapper\OnMissingProperties; class Order { public function __construct( public readonly int $id, public readonly ?string $customerName, ) { } } $json = '{ "id": 1 }'; $mapper = new JsonMapper( onMissingProperties: OnMissingProperties::SET_NULL, ); $order = $mapper->map($json, Order::class); var_export($order->customerName); // NULL
如果属性缺失且参数不可空,则无论此选项如何,都会抛出异常。
-
OnMissingProperties::SET_DEFAULT
如果 JSON 属性缺失且参数有默认值,则
JsonMapper
将参数设置为它的默认值use Brick\JsonMapper\JsonMapper; use Brick\JsonMapper\OnMissingProperties; class Order { public function __construct( public readonly int $id, public readonly string $customerName = 'no name', ) { } } $json = '{ "id": 1 }'; $mapper = new JsonMapper( onMissingProperties: OnMissingProperties::SET_DEFAULT, ); $order = $mapper->map($json, Order::class); var_export($order->customerName); // 'no name'
如果属性缺失且参数没有默认值,则无论此选项如何,都会抛出异常。
-
-
$jsonToPhpNameMapper
&$phpToJsonNameMapper
默认情况下,
JsonMapper
假设 JSON 属性名称与 PHP 参数名称相同。通过提供
NameMapper
接口的实现,您可以自定义两者之间的映射。该库为常见用例提供了两个实现
SnakeCaseToCamelCaseMapper
将转换snake_case
到camelCase
CamelCaseToSnakeCaseMapper
将转换camelCase
到snake_case
示例
use Brick\JsonMapper\JsonMapper; use Brick\JsonMapper\NameMapper\CamelCaseToSnakeCaseMapper; use Brick\JsonMapper\NameMapper\SnakeCaseToCamelCaseMapper; class Order { public function __construct( public readonly int $id, public readonly int $amountInCents, public readonly string $customerName, ) { } } $json = '{ "id": 1, "amount_in_cents": 2499, "customer_name": "John Doe" }'; $mapper = new JsonMapper( jsonToPhpNameMapper: new SnakeCaseToCamelCaseMapper(), phpToJsonNameMapper: new CamelCaseToSnakeCaseMapper(), ); $order = $mapper->map($json, Order::class); echo $order->amountInCents; // 2499 echo $order->customerName; // 'John Doe'