square / pjson
JSON 与 PHP 模型序列化/反序列化库。直接将 JSON 反序列化为 PHP 对象模型类。
Requires
- php: >=8.0
Requires (Dev)
- orchestra/testbench: ^7.11
- phpstan/phpstan: ^1.8
- phpunit/phpunit: ^9.5
- squizlabs/php_codesniffer: ^3.7
- symfony/var-dumper: ^6.0
This package is auto-updated.
Last update: 2024-09-15 19:40:02 UTC
README
一个简单的 JSON 到 PHP 对象转换库
我们经常需要与返回 JSON 的 API 或数据源进行交互。PHP 只提供将 JSON 反序列化为数组或 stdClass 类型对象的可能性。
此库帮助将 JSON 反序列化为自定义定义的类的实际对象。它通过使用 PHP8 类属性的属性来实现。
示例
类的简单序列化
use Square\Pjson\Json; use Square\Pjson\JsonSerialize; class Schedule { use JsonSerialize; #[Json] protected int $start; #[Json] protected int $end; public function __construct(int $start, int $end) { $this->start = $start; $this->end = $end; } } (new Schedule(1, 2))->toJson();
将产生以下结果
{ "start": 1, "end": 2 }
然后可以通过以下方式实现反向操作
Schedule::fromJsonString('{"start":1,"end":2}');
这将返回一个 Schedule
类的实例,其属性根据 JSON 设置。
自定义名称
上一个示例可以修改为在 JSON 中使用自定义名称而不是仅使用属性名称
use Square\Pjson\Json; use Square\Pjson\JsonSerialize; class Schedule { use JsonSerialize; #[Json('begin')] protected int $start; #[Json('stop')] protected int $end; public function __construct(int $start, int $end) { $this->start = $start; $this->end = $end; } } (new Schedule(1, 2))->toJson();
将产生以下结果
{ "begin": 1, "stop": 2 }
使用这些新的属性名称进行反序列化与之前一样工作
dump(Schedule::fromJsonString('{"begin":1,"stop":2}')); // ^ Schedule^ {#345 // #start: 1 // #end: 2 // }
私有/受保护
属性的可见性无关紧要。私有或受保护的属性也可以进行序列化和反序列化(请参阅前面的示例)。
属性路径
有时 JSON 格式与我们想要的 PHP 版本不完全一致。例如,假设我们收到的用于前面调度示例的 JSON 格式如下
{ "data": { "start": 1, "end": 2 } }
通过如下声明我们的类 json 属性,我们仍然可以直接将这些属性读取到我们的类中
class Schedule { use JsonSerialize; #[Json(['data', 'start'])] protected int $start; #[Json(['data', 'end'])] protected int $end; public function __construct(int $start, int $end) { $this->start = $start; $this->end = $end; } }
递归序列化/反序列化
如果我们正在处理一个稍微复杂一点的 JSON 结构,我们希望属性是类,可以正确地反序列化。
{ "saturday": { "start": 0, "end": 2 }, "sunday": { "start": 0, "end": 7 } }
以下 2 个 PHP 类可以很好地与此一起使用
class Schedule { use JsonSerialize; #[Json] protected int $start; #[Json] protected int $end; } class Weekend { use JsonSerialize; #[Json('saturday')] protected Schedule $sat; #[Json('sunday')] protected Schedule $sun; }
数组
当我们处理一个数组的项目时,其中每个项目都应该是一个给定的类,我们需要告诉 pjson 目标类型
{ "days": [ { "start": 0, "end": 2 }, { "start": 0, "end": 2 }, { "start": 0, "end": 2 }, { "start": 0, "end": 7 } ] }
假设 Schedule 仍然定义如前,我们可以定义一个星期如下
class Week { use JsonSerialize; #[Json(type: Schedule::class)] protected array $days; }
如果 JSON 如下,这也会工作
{ "days": { "monday": { "start": 0, "end": 2 }, "wednesday": { "start": 0, "end": 2 } } }
生成的 PHP 对象将是
Week^ {#353
#days: array:2 [
"monday" => Schedule^ {#344
#start: 0
#end: 2
}
"wednesday" => Schedule^ {#343
#start: 0
#end: 2
}
]
}
集合类
类似于数组,您可能希望使用集合类。只要您的类实现 Traversable
接口,您就可以这样做。在这种情况下,pjson 将默认尝试通过构造函数传递数据数组来构造您的类。如果这不适合您,您可以为您的集合指定一个自定义工厂方法
class Collector { use JsonSerialize; #[Json(type: Schedule::class)] public Collection $schedules; #[Json(type: Schedule::class, collection_factory_method: 'make')] public Collection $static_factoried_schedules; #[Json(type: Schedule::class, collection_factory_method: 'makeme')] public Collection $factoried_schedules; }
在我们的示例中,集合有一个静态工厂方法 make
和一个实例方法 makeme
,它们都可以使用。构造函数选项也有效。您可以在 tests/Definitions
目录中查看集合类。
这允许您使用类似以下内容的 JSON 进行操作
{ "schedules": [ { "schedule_start": 1, "schedule_end": 2 } ], "factoried_schedules": [ { "schedule_start": 10, "schedule_end": 20 } ], "static_factoried_schedules": [ { "schedule_start": 100, "schedule_end": 200 } ] }
多态反序列化
假设您有两个继承自基类的类。您可能将它们作为集合接收,并且事先不知道您将处理其中一个还是另一个。例如
abstract class CatalogObject { use JsonSerialize; #[Json] protected $id; #[Json] protected string $type; } class CatalogCategory extends CatalogObject { use JsonSerialize; #[Json('parent_category_id')] protected string $parentCategoryId; } class CatalogItem extends CatalogObject { use JsonSerialize; #[Json] protected string $name; }
您可以在 CatalogObject
上实现 fromJsonData(array $array) : static
以根据接收到的数据进行区分并返回正确的序列化
abstract class CatalogObject { use JsonSerialize; #[Json] protected $id; #[Json] protected string $type; public static function fromJsonData($jd): static { $t = $jd['type']; return match ($t) { 'category' => CatalogCategory::fromJsonData($jd), 'item' => CatalogItem::fromJsonData($jd), }; } }
警告:请确保每个子类直接 use JsonSerialize
。否则,当它们调用 ::fromJsonData
时,它们将调用 CatalogObject
的父类,导致无限递归。
有了这个,我们可以做
$jsonCat = '{"type": "category", "id": "123", "parent_category_id": "456"}'; $c = CatalogObject::fromJsonString($jsonCat); $this->assertEquals(CatalogCategory::class, get_class($c)); $jsonItem = '{"type": "item", "id": "123", "name": "Sandals"}'; $c = CatalogObject::fromJsonString($jsonItem); $this->assertEquals(CatalogItem::class, get_class($c));
列表
如果您正在处理要反序列化的东西的列表,您可以使用 MyClass::listFromJsonString($json)
或 MyClass::listfromJsonData($array)
。例如
Schedule::listFromJsonString('[ { "schedule_start": 1, "schedule_end": 2 }, { "schedule_start": 11, "schedule_end": 22 }, { "schedule_start": 111, "schedule_end": 222 } ]');
产生与以下相同的结果
[ new Schedule(1, 2), new Schedule(11, 22), new Schedule(111, 222), ];
初始路径
有时你关心的JSON会嵌套在一个属性下,但你不需要/不需要对最外层进行建模。为此,你可以将一个$path
传递给反序列化方法。
Schedule::fromJsonString('{ "data": { "schedule_start": 1, "schedule_end": 2 } }', path: 'data'); Schedule::fromJsonString('{ "data": { "main": { "schedule_start": 1, "schedule_end": 2 } } }', path: ['data', 'main']);
枚举
支持在PHP 8.1中直接使用支持的后备枚举。
class Widget { use JsonSerialize; #[Json] public Status $status; } enum Status : string { case ON = 'ON'; case OFF = 'OFF'; } $w = new Widget; $w->status = Status::ON; $w->toJson(); // {"status": "ON"}
并且可以通过JsonSerialize
特质或JsonDataSerializable
接口来支持常规枚举。
class Widget { use JsonSerialize; #[Json] public Size $size; } enum Size { use JsonSerialize; case BIG; case SMALL; public static function fromJsonData($d, array|string $path = []): static { return match ($d) { 'BIG' => self::BIG, 'SMALL' => self::SMALL, 'big' => self::BIG, 'small' => self::SMALL, }; } public function toJsonData() { return strtolower($this->name); } } $w = new Widget; $w->size = Size::BIG; $w->toJson(); // {"status": "big"}
必需属性
你可以标记一个属性在反序列化时为必需。
readonly class Token { use JsonSerialize; #[Json(required: true)] public string $key; } $token = Token::fromJsonString('{"key":"data"}'); // successful Token::fromJsonString('{"other":"has no key"}'); // throws Exception
标量 <=> 类
在某些情况下,你可能希望在反序列化后标量值变成PHP对象,反之亦然。例如,一个BigInt
类可以持有字符串形式的int,并在序列化为JSON时将其表示为字符串。
class Stats { use JsonSerialize; #[Json] public BigInt $count; } class BigInt implements JsonDataSerializable { public function __construct( protected string $value, ) { } public static function fromJsonData($jd, array|string $path = []) : static { return new BigInt($jd); } public function toJsonData() { return $this->value; } } $stats = new Stats; $stats->count = new BigInt("123456789876543234567898765432345678976543234567876543212345678765432"); $stats->toJson(); // {"count":"123456789876543234567898765432345678976543234567876543212345678765432"}
集合类
如果你希望使用pjson与集合类一起使用
与PHPStan一起使用
使用此库时,你可能会有一些属性看起来在代码的任何地方都没有被读取或写入,但它们仅用于JSON序列化。PHPStan会对此类问题提出警告,但你可以在你的phpstan.neon
中添加此库的扩展,以帮助PHPStan理解这是预期行为。
includes: - vendor/square/pjson/extension.neon
Laravel集成
通过可转换类型
如果你希望通过Pjson将Eloquent模型属性转换为类,你可以使用提供的转换实用工具来实现。
use Illuminate\Contracts\Database\Eloquent\Castable; use Square\Pjson\Json; use Square\Pjson\JsonSerialize; use Square\Pjson\Integrations\Laravel\JsonCastable; class Schedule implements Castable // implement the laravel interface { use JsonSerialize; use JsonCastable; // use the provided Pjson trait #[Json] protected int $start; #[Json] protected int $end; public function __construct(int $start, int $end) { $this->start = $start; $this->end = $end; } }
然后在你的Eloquent模型中
$casts = [ 'schedule' => Schedule::class, ];
通过转换参数
或者,你可以简单地使用Laravel的转换参数。在这种情况下,Schedule
类保持不变。
use Square\Pjson\Json; use Square\Pjson\JsonSerialize; class Schedule { use JsonSerialize; #[Json] protected int $start; #[Json] protected int $end; public function __construct(int $start, int $end) { $this->start = $start; $this->end = $end; } }
并且你提供转换的目标类
$casts = [ 'schedule' => JsonCaster::class.':'.Schedule::class, ];