ddn / jsonobject
JsonObject辅助类,简化PHP中处理JSON对象的过程
Requires
- php: >=7.4
README
JsonObject
是一个PHP类,用于简化从JSON定义中使用对象的过程。这个想法来自于使用Python中的 pydantic
,以及它将解析和验证JSON数据到对象的能力。
为什么使用 JsonObject
我需要使用PHP的API,并且该API返回JSONObjects。因此,我需要将它们解析成PHP对象,以便在应用程序中使用。
工作流程如下
- 检索JSON对象定义
- 使用
JsonObject
解析JSON定义 - 在应用程序中使用生成的对象
用例
让我们看一下以下JSON示例
{ "id": 0, "name": "John Doe", "age": 42, "emails": [ "my@email.com", "other@email.com" ], "address": { "street": "My street", "number": 42, "city": "My city", "country": "My country" } }
使用 JsonObject
,我能够使用以下类定义我的数据模型
class User extends JsonObject { const ATTRIBUTES = [ 'id' => 'int', 'name' => 'str', 'age' => 'int', 'emails' => 'list[str]', 'address?' => 'Address', ]; } class Address extends JsonObject { const ATTRIBUTES = [ 'street' => 'str', 'number' => 'int', 'city' => 'str', 'country' => 'str', ]; }
然后添加以下命令
$user = User::fromObject(json_decode($json_text_definition));
JsonObject
类将执行将内容解析为对象的操作,我们将能够使用其定义的属性
echo($user->name);
定义的类也可以有方法,这将使得实现应用程序的数据模型更加容易。例如,可以像这样定义类 User
class User extends JsonObject { const ATTRIBUTES = [ 'id' => 'int', 'name' => 'str', 'age' => 'int', 'emails' => 'list[str]', 'address?' => 'Address', ]; public function isAdult() { return $this->age >= 18; } }
使用 JsonObject
JsonObject
类的想法是使用它将JSON数据解析为对象,以便这些对象可以包含其他方法,有助于实现应用程序的数据模型。
当解析JSON对象(或数组)时,其内容将根据在 ATTRIBUTES
常量中定义的类型进行递归解析。如果数据无效,因为它不包含预期的值,将抛出异常。
要使用JsonObject,必须从 JsonObject
继承,并为该类定义 ATTRIBUTES
常量,以定义该类期望的对象的属性以及每个属性的类型。
定义属性的类型
ATTRIBUTES
常量是一个关联数组,键是每个属性的 名称,值是每个属性的 类型。
可能类型包括:
- int:整型数字
- float:浮点数
- str:字符串
- bool:布尔值
- list[type]:类型为 type 的对象列表。
- dict[type]:类型为 type 的对象字典。字典条目的键被转换为字符串。
- object:是一个类名,它必须是
JsonObject
的子类。
可选属性和必选属性
在定义属性名称时,可以在名称末尾添加一个 ?
以指示该属性是可选的。例如,用例部分中的属性名称 address?
是可选的。
每个字段都认为是必选的,这意味着它必须在解析的对象(或数组)中存在。此外,对象必须属于定义的类型(即它必须被特定类型正确解析)。
必选属性
任何非可选属性都认为是必选的。这有两个特别需要注意的点
- 从外部结构创建对象时(无论是使用
fromArray
或fromObject
函数)。 - 生成 对象 或 数组 表示形式的
JsonObject
当从外部结构创建对象时,JsonObject
将处理所有必填字段。如果其中任何一个字段缺失,将引发异常。
在下一个示例中,将引发异常,因为必填字段年龄未提供。
class User extends JsonObject { const ATTRIBUTES = [ "name" => "str", "age" => "int", ]; } (...) $user = User::fromArray([ "name" => "John" ]);
当将对象转换为数组或对象(或获取其json表示形式)时,即使未设置,必填字段也会获得默认值。
因此,在下一个示例中
class User extends JsonObject { const ATTRIBUTES = [ "name" => "str", "age" => "int", "birthDate?" => "str" ]; } $user = new User(); echo((string)$user);
输出将如下
{ "name": "", "age": 0 }
因为虽然属性姓名和年龄是必填的,并且它们获得了它们的默认值(即数字为0,字符串为空),属性出生日期不是必填的,并且尚未设置。所以它不会在输出中生成。
将必填属性设置为null
将值设置为null的问题在考虑一个属性是否可选时具有特殊的相关性。
有人可能认为,如果我们将值设置为null,这意味着要取消设置该值,因此它应该只适用于可选值,而不适用于必填值。
在JsonObject
中,我们有一个不同的概念,因为将属性设置为null将意味着“将值设置为null”,而不是取消属性。为了取消属性,我们应该使用unset
函数或类似函数。
取消必填属性
JsonObject
也允许取消值。对于可选属性,这意味着删除值,因此它在一个数组表示形式或对象中(如果检索值,它将被设置为null)将不会有任何值。
但对于必填属性,取消它意味着将其值重置为默认值。这意味着它将被初始化为该类型的默认值(即数字为0,列表、字符串或字典为空等),或者其默认值在ATTRIBUTES
常量中。
继承
JsonObject
也能从其父类继承属性。以下是一个示例
class Vehicle extends JsonObject { const ATTRIBUTES = [ "brand" => "str", "color" => "str" ] } class Car extends Vehicle { const ATTRIBUTES = [ "wheels" => "int" ] } class Boat extends Vehicle { const ATTRIBUTES = [ "length" => "float" ] }
在这个示例中,类Vehicle
将只有属性品牌和颜色,但类Car
将会有品牌、颜色和轮子属性,而类Boat
将会有品牌、颜色和长度属性。
对象的创建
可以从JsonObject
的子类创建对象,使用静态方法::fromArray
或::fromObject
,从一个解析的json对象开始。
在先前的示例中,如果我们有一个名为car.json的文件,其内容如下
{ "brand": "BMW", "color": "black" }
我们可以使用以下代码获取Vehicle
类的实例
$json = file_get_contents("car.json"); $vehicle = Vehicle::fromArray((array)json_decode($json, true));
另一种方法是像下面示例那样实例化对象
* PHP 8及以上
$car = new Car(brand: "BMW", color: "black", wheels: 4);
*之前的PHP版本
$car = new Car([ "brand" => "BMW", "color" => "black", "wheels" => 4]);
对象的方法
JsonObject
JsonObject
JsonObject
是此库的核心类。它有以下方法:
__construct($data)
- 从给定的数据创建一个新对象__get($name)
- 返回具有给定名称的属性的值__set($name, $value)
- 设置具有给定名称的属性的值__isset($name)
- 如果具有给定名称的属性已设置,则返回true__unset($name)
- 取消可选属性的值(或重置必填属性的值)。toArray()
- 返回包含对象数据的关联数组。数组递归创建,访问每个属性的每个子属性。toObject()
- 返回包含对象数据的对象,作为属性。数组递归创建,访问每个属性的每个子属性。toJson()
- 返回一个表示对象的标准对象的JSON字符串。::fromArray($data)
- 通过解析给定的关联数组到类中定义的属性来创建一个对象。每个属性都按其定义的类型递归解析。::fromObject($data)
- 通过解析给定的对象到类中定义的属性来创建一个对象。每个属性都按其定义的类型递归解析。
JsonDict
该对象用于处理来自JSON定义的字典。JsonDict
类要求每个元素都必须来自给定的类型。
JsonDict
对象可以用作类似数组的对象(例如 $jsonDict["key1"]),但在撰写本文时,字典中插入的元素类型未进行检查。类型用于在创建字典时(例如使用 fromArray
静态函数)或将内容导出到数组或对象(例如使用 toArray
函数)时解析内容。
方法包括
toArray()
toObject()
::fromArray($data)
::fromObject($data)
这些方法与JsonObject
的情况解释相同。字典中元素的类型可能引用复杂类型,在解析内容时会递归考虑。
例如,类型 list[list[int]]
将用于解析 [ [ 1, 2, 3], [ 4, 5, 6 ]]
JsonArray
这个对象与JsonDict
非常相似,区别在于索引必须是整数。在这种情况下,$value["key1"]
将引发异常。
在这种情况下,实现向数组中添加元素(即[]
)的函数。
初始化值
在定义类时,可以为新创建的对象以及那些可选属性初始化值。
有两种方式
### 使用类属性
可以通过使用类属性来初始化对象值,因此如果属性值在类中设置,它将被复制到实例中作为属性,如果它已定义。
例如:
class User extends JsonObject { const ATTRIBUTES = [ 'id' => 'int', 'name' => 'str', 'age' => 'int', 'emails' => 'list[str]', 'address?' => 'Address', 'sex?' => 'str' ]; public $sex = "not revealed"; }
现在,属性 sex
被初始化为 未公开 而不是 null。
使用属性的说明
要这样做,请为对象的类型定义一个 [ <type>, <default value> ]
元组。以下一个示例为例
class User extends JsonObject { const ATTRIBUTES = [ 'id' => 'int', 'name' => 'str', 'age' => 'int', 'emails' => 'list[str]', 'address?' => 'Address', 'sex?' => [ 'str', 'not revealed' ] ]; }
当检索用户数据时,属性 sex
是可选的。使用这个新的类定义,如果 sex
未设置,则值将设置为 "未公开" 而不是 null
。
一个重要特性是,如果设置为 <default value> 的字符串是对象的某个方法,则在获取值时将调用该方法(如果尚未设置),并且为该属性设置的值将是该调用的结果。
例如:
class User extends JsonObject { const ATTRIBUTE = [ ... 'birthDay?' => [ 'str', 'computeBirthDate' ] ] function computeBirthDate() { $now = new DateTime(); $now->sub(DateInterval::createFromDateString("{$this->age} years")); return $now->format("Y-m-d"); } }
在这个例子中,如果我们没有设置 birthDate
属性,但是检索它,它将通过从当前日期减去年龄来计算。
其他工具和技术事实
解析值
如果要将任意对象解析为JsonObject
,可以使用函数JsonObject::parse_typed_value
。这很重要,可以将任何类型转换为JsonObject
类型。
例如:
$myobject = JsonObject::parse_typed_value("list[str]", [ "my", "name", "is", "John" ]);
将获得一个类型为 JsonList<str>
的对象。
类型检查
该库的默认行为是确保设置的属性值与其定义的类型匹配。但这意味着,由于浮点数(float)不是整数(int),将浮点数设置为0
将会失败,因为0
是一个整数。在这种情况下,用户必须在赋值之前对值进行类型转换。要控制是否严格检查类型,可以使用常量STRICT_TYPE_CHECKING
。
如果将
STRICT_TYPE_CHECKING
设置为True
,类型将被严格检查,例如将9.3
赋值给int
将引发异常。如果设置为False
,数值类型将相互转换。例如,如果我们将9.3
赋值给int
,它将被自动截断为9
。
其他重要的类型检查是在将空值(即""
或null
)赋值给数值类型时。在这种情况下,我们有一个常量STRICT_TYPE_CHECKING_EMPTY_ZERO
。
如果将
STRICT_TYPE_CHECKING_EMPTY_ZERO
设置为True
(默认行为),当将空值赋给数值类型时,它将被视为0
。即,将空字符串或null
值赋给int
属性,意味着赋值为0
。如果设置为False
,库将检查类型,并最终引发异常。
增强的JsonLists
现在JsonList
也支持使用负索引,因此-1
将是最后一个元素,-2
是倒数第二个,等等。
JsonList
对象包含排序或过滤的函数。
public function sort(callable $callback = null) : JsonList
:使用给定的回调函数对列表进行排序。如果没有提供回调函数,将使用默认的比较函数对列表进行排序。public function filter(callable $callback) : JsonList
:使用给定的回调函数过滤列表。回调函数必须返回一个布尔值。如果回调返回true
,则元素将被包含在结果列表中。如果返回false
,则元素将被丢弃。