netresearch/jsonmapper

将嵌套 JSON 结构映射到 PHP 类

安装次数: 71,769,632

依赖: 322

建议者: 2

安全: 0

星标: 1,557

关注者: 49

分支: 184

开放问题: 2

v5.0.0 2024-09-08 10:20 UTC

README

将来自 JSON 网服务的数据转换为嵌套对象和数组 - 使用您自己的模型类。

从基础对象开始,它将 JSON 数据映射到类属性上,将它们转换为正确的简单类型或对象。

这与 PHP 的 SoapClient 提供的本地 SOAP 参数映射类似,但用于 JSON。它不依赖于任何模式,只依赖于您的 PHP 类定义。

类型检测通过解析类型声明和类属性的 @var 文档注释以及设置方法中的类型提示来实现。

您不需要通过添加 JSON 特定代码来修改您的模型类;它通过解析已存在的文档注释自动工作。

此库没有依赖。

关键词:反序列化,活化

内容

利弊

好处

  • IDE 中的自动完成
  • 轻松向数据模型类添加便捷方法
  • 您的 JSON API 可能会更改,但您的模型可以保持不变 - 不会破坏使用模型类的应用程序。

缺点

  • 模型类需要手动编写

    由于 JsonMapper 不依赖于任何模式信息(例如来自 json-schema),因此模型类不能自动生成。

用法

基本用法

  1. 安装 netresearch/jsonmapper 使用 composer
  2. 创建一个 JsonMapper 对象实例
  3. 根据您的数据调用 mapmapArray 方法

映射普通对象

<?php
require 'autoload.php';
$mapper = new JsonMapper();
$contactObject = $mapper->map($jsonContact, new Contact());
// or as classname
$contactObject = $mapper->map($jsonContact, Contact::class);

映射对象数组

<?php
require 'autoload.php';
$mapper = new JsonMapper();
$contactsArray = $mapper->mapArray(
    $jsonContacts, array(), 'Contact'
);

除了 array(),您还可以使用 ArrayObject 和其派生类,以及实现 ArrayAccess 的类。

示例

来自地址簿网络服务的 JSON

{
    "name":"Sheldon Cooper",
    "address": {
        "street": "2311 N. Los Robles Avenue",
        "city": "Pasadena"
    }
}

您本地的 Contact

<?php
class Contact
{
    /**
     * Full name
     */
    public string $name;

    public ?Address $address;
}

您本地的 Address

<?php
class Address
{
    public $street;
    public $city;

    public function getGeoCoords()
    {
        //do something with $street and $city
    }
}

您的应用程序代码

<?php
$json = json_decode(file_get_contents('http://example.org/sheldon.json'));
$mapper = new JsonMapper();
$contact = $mapper->map($json, new Contact());

echo "Geo coordinates for " . $contact->name . ": "
    . var_export($contact->address->getGeoCoords(), true);

属性类型映射

JsonMapper 使用以下顺序的几个来源来检测属性的正确类型

  1. 设置方法(set + ucwords($propertyname)

    下划线 "_" 和连字符 "-" 将下一个字母转换为大写。属性 foo_bar-baz 导致设置方法 setFooBarBaz

    1. 如果方法签名中有类型提示,则使用该类型

      public function setPerson(Contact $person) {...}
      
    2. 检查方法的文档注释以查找 @param $type 注释

      /**
       * @param Contact $person Main contact for this application
       */
      public function setPerson($person) {...}
      
    3. 如果无法检测到类型,则将纯 JSON 值传递给设置方法。

  2. 类属性类型(自 PHP 7.4 以来)

    public Contact $person;
    
  3. 构造函数属性提升类型(自 PHP 8.0 以来)

    public function __construct(protected Contact $person) {}
    
  4. @var $type 类属性文档注释

    /**
     * @var \my\application\model\Contact
     */
    public $person;
    

    属性必须是公开的才能直接使用。您还可以使用 $bIgnoreVisibility 来使用受保护和私有属性。

    如果无法检测到类型,属性将获得纯JSON值设置。

    如果找不到属性,JsonMapper 将尝试以不区分大小写的方式找到该属性。然后 JSON 属性 isempty 将映射到 PHP 属性 isEmpty

    注意

    必须提供完全限定的命名空间以使类型工作。相对类名在当前类命名空间中评估,而不考虑可能存在的任何导入。

    PHP 不通过反射提供导入;注释文本仅包含类型的文字文本。出于性能原因,JsonMapper 不会自行解析源代码以检测和扩展任何导入。

支持的类型名称

  • 简单类型

    • 字符串
    • boolboolean
    • intinteger
    • doublefloat
    • 数组
    • 对象
    • 混合
  • 类名称,包括和不包括命名空间

    • Contact - 如果 JSON 值是 null,则抛出异常
  • 简单类型和类名称的数组

    • int[]
    • Contact[]
  • 多维数组

    • int[][]
    • TreeDeePixel[][][]
  • 简单类型和类名称的 ArrayObjects

    • ContactList[Contact]
    • NumberList[int]
  • 带和不带命名空间的受支持枚举

    • Suit:string|Suit:int - 如果 JSON 值不在枚举中,则抛出异常
  • 可空类型

    • int|null?int - 如果 JSON 中的值为 null,则将是 null,否则它将是整数
    • Contact|null?Contact - 如果 JSON 中的值为 null,则将是 null,否则它将是类型为 Contact 的对象

ArrayObjects 和扩展类被视为数组。

没有类型或类型为 mixed 的变量将直接设置 JSON 值,而无需任何转换。

有关更多信息,请参阅 phpdoc 的类型文档

简单类型映射

注意

由于安全原因,从版本 5 开始默认禁用此功能。有关详细信息,请参阅 $bStrictObjectTypeChecking

当应该创建一个对象但 JSON 仅包含简单类型(例如字符串、浮点数、布尔值)时,此值将传递给类的构造函数。示例

PHP 代码

public DateTime $date;

JSON

{"date":"2014-05-15"}

这将导致调用 new DateTime('2014-05-15')

类映射

当变量定义为抽象类或接口的对象时,JsonMapper 通常会尝试直接实例化这些类并崩溃。

使用 JsonMapper 的 $classMap 属性,您可以指定哪些类应被实例化

$jm = new JsonMapper();
$jm->classMap['Foo'] = 'Bar';
$jm->map(...);

这将创建类型为 Bar 的对象,当变量定义为类型为 Foo 时。

如果实际实现类需要动态确定(例如在联合的情况下),还可以使用可调用。映射类(下面的示例中的 'Foo')和 Json 数据作为参数传递到调用中。

$mapper = function ($class, $jvalue) {
    // examine $class and $jvalue to figure out what class to use...
    return 'DateTime';
};

$jm = new JsonMapper();
$jm->classMap['Foo'] = $mapper;
$jm->map(...);

可空

除非 PHP 类属性具有可空类型 - 例如 Contact|null?Contact,否则 JsonMapper 在 JSON 属性为 null 时抛出异常。

如果您的 API 中包含许多可能为 null 的字段,并且您不希望使所有类型定义都为可空的,请设置

$jm->bStrictNullTypes = false;

从 5.0.0 版本开始,如果类型不是可空的 - 例如 array[?string]array[string|null],则数组中的 null 值会导致 JsonMapper_Exception

要恢复以前的行为(允许 null,即使未声明),请设置

$jm->bStrictNullTypesInArrays = false;

日志记录

JsonMapper的setLogger()方法支持所有与PSR-3兼容的日志记录实例。

被记录的事件

  • JSON数据包含一个键,但类中没有相应的属性或设置方法。
  • 由于它们是受保护的或私有的,因此不能从外部设置设置器或属性。

处理无效或缺失数据

在开发过程中,API经常会发生变化。为了通知此类更改,可以配置JsonMapper在数据缺失或尚未知的情况下抛出异常。

未知属性

当JsonMapper看到JSON数据中定义的PHP类中未定义的属性时,您可以通过设置$bExceptionOnUndefinedProperty让它抛出异常。

$jm = new JsonMapper();
$jm->bExceptionOnUndefinedProperty = true;
$jm->map(...);

您还可以通过将callable设置到$undefinedPropertyHandler来自定义处理这些属性。

/**
 * Handle undefined properties during JsonMapper::map()
 *
 * @param object $object    Object that is being filled
 * @param string $propName  Name of the unknown JSON property
 * @param mixed  $jsonValue JSON value of the property
 *
 * @return void
 */
function setUndefinedProperty($object, $propName, $jsonValue)
{
    $object->{'UNDEF' . $propName} = $jsonValue;
}

$jm = new JsonMapper();
$jm->undefinedPropertyHandler = 'setUndefinedProperty';
$jm->map(...);

或者,如果您希望JsonMapper为您处理设置器,您可以从$undefinedPropertyHandler返回一个字符串,该字符串将用作属性名。

/**
 * Handle undefined properties during JsonMapper::map()
 *
 * @param object $object    Object that is being filled
 * @param string $propName  Name of the unknown JSON property
 * @param mixed  $jsonValue JSON value of the property
 *
 * @return void
 */
function fixPropName($object, $propName, $jsonValue)
{
    return ucfirst($propName);
}

$jm = new JsonMapper();
$jm->undefinedPropertyHandler = 'fixPropName';
$jm->map(...);

缺失属性

注意

这仅在$bStrictObjectTypeChecking保持启用时有效。

您可以通过在它们的docblock中放置@required来将PHP类中的属性标记为“必需”。

/**
 * @var string
 * @required
 */
public $someDatum;

如果JSON数据不包含此属性,并且激活了$bExceptionOnMissingData,JsonMapper将在出现JsonMapper_Exception时抛出异常。

$jm = new JsonMapper();
$jm->bExceptionOnMissingData = true;
$jm->map(...);

选项$bRemoveUndefinedAttributes会导致JsonMapper在最终对象中删除不在JSON数据中的属性。

$jm = new JsonMapper();
$jm->bRemoveUndefinedAttributes = true;
$jm->map(...);

私有属性和函数

您可以通过将$bIgnoreVisibility设置为true来允许将映射到私有和受保护的属性和设置器方法。

$jm = new JsonMapper();
$jm->bIgnoreVisibility = true;
$jm->map(...);

简单类型而不是对象

当变量的类型是类且JSON数据是简单类型(如string)时,如果配置为这样做,JsonMapper可以将此值传递给类的构造函数。

$jm = new JsonMapper();
$jm->bStrictObjectTypeChecking = false;
$jm->map(...);

这可以用于从日期字符串自动初始化DateTime对象。

虽然禁用这种严格对象类型检查可能会导致问题。

  • 当类没有构造函数或没有构造函数参数时,值将会丢失。
  • 当构造函数有多个必需参数时,它将崩溃。
  • 当构造函数的参数类型与JSON中的数据类型不匹配时,它将崩溃。
  • @required属性将不会被填充。

注意

从版本5开始,默认值从false更改为true,以提高安全性。

现在,如果您想将简单类型传递给类构造函数,您必须选择启用。

传递数组到 map()

您可能希望将通过调用得到的数组数据传递给map()

json_decode($jsonString, true)

默认情况下,JsonMapper会抛出异常,因为map()需要对象作为第一个参数。您可以通过将$bEnforceMapType设置为false来规避这一点。

$jm = new JsonMapper();
$jm->bEnforceMapType = false;
$jm->map(...);

映射后回调

JsonMapper能够在映射完成后直接调用每个对象的自定义方法。

$jm = new JsonMapper();
$jm->postMappingMethod = 'afterMapping';
$jm->map(...);

现在,对于每个映射的对象(如果类具有该方法),都会调用afterMapping()

您可以向后映射回调传递额外的参数。

$jm = new JsonMapper();
$jm->postMappingMethod = 'afterMapping';
$jm->postMappingMethodArguments = [23, 'foo'];
$jm->map(...);

安装

通过Packagist使用Composer。

$ composer require netresearch/jsonmapper

相关软件

替代方案

关于 JsonMapper

许可证

JsonMapper 采用 OSL 3.0 许可协议。

编码风格

JsonMapper 遵循 PEAR 编码标准

作者

Christian Weiskecweiske.de