php 巧用 json mapper 来处理 json - (sunznx) 振翅飞翔
10 July 2019

长话短说 json mapper 是一个将 json 自动序列化为对象的一个工具。项目地址是 https://github.com/cweiske/jsonmapper

他能自动解析变量的类型提示,拿到类型提示之后再通过反射,实例化字段对应的类,比如

class Order
{
    /** @var string */
    public $order_id;

    /** @var Goods[] */
    public $goods;
}

class Goods
{
    /** @var string */
    public $name;

    /** @var double */
    public $price;
}
$json = <<< 'EOF'
{
    "order_id": "12345",
    "goods": [
        {
            "name": "goods1",
            "price": 12.1,
            "color": "red"
        },
        {
            "name": "goods2",
            "price": 12.99,
            "color": "yellow"
        }
    ]
}
EOF;

$obj = json_decode($json);
$mapper = new JsonMapper();
$order = $mapper->map($obj, new Order());
print_r($order);

// App\Order Object
// (
//     [order_id] => 12345
//     [goods] => Array
//         (
//             [0] => App\Goods Object
//                 (
//                     [name] => goods1
//                     [price] => 12.1
//                 )
//             [1] => App\Goods Object
//                 (
//                     [name] => goods2
//                     [price] => 12.99
//                 )
//         )
// )

使用的过程,发现的几个坑

  1. 默认的 map 函数处理的并不友好

    $mapper = new JsonMapper();
    $order = $mapper->map($obj, new Order());
    
    class JsonMapper
    {
        ...
    
        public function map($json, $object)
        {
            if ($this->bEnforceMapType && !is_object($json)) {
                throw new InvalidArgumentException(...);
            }
    
            foreach ($json as $key => $jvalue) {
                ...
            }
        }
    }
    

    JsonMapper 这个类有个属性 bEnforceMapType 默认是 false ,如果传进来的 $json 不是 object 的时候,会抛异常(实际上合理的应该是,如果是数组类型不应该抛异常,大部分情况下,都是 json_decode(xxx, true) 来处理 json 的)。还有更合理的情况是,判断是字符串的时候,自动帮我们去做一个 json_decode

  2. 键值不存在的时候,处理得并不友好

    class Goods
    {
        /** @var string */
        public $name;
    
        /** @var double */
        public $price;
    }
    
    {
        "order_id": "12345",
        "goods": [
            {
                "name": "goods1",
                "price": 12.1,
                "color": "red"
            },
            {
                "name": "goods2",
                "price": 12.99,
                "color": "yellow"
            }
        ]
    }
    
    App\Order Object (
      [order_id] => 12345
      [goods] => Array (
        [0] => App\Goods Object (
          [name] => goods1
          [price] => 12.1
        )
    
        [1] => App\Goods Object (
          [name] => goods2
          [price] => 12.99
        )
      )
    )
    

    对比上面两串,实际上我们可以看出,当 json 对应的 field 不在 class 的定义里的时候,这个键在反序列化的时候不会存在。JsonMapper 有一个属性 undefinedPropertyHandler 是用来处理这种情况的

    $mapper = new JsonMapper();
    $mapper->bEnforceMapType = false;  // 默认只能转换 Object,设置该值为 true,允许转换数组格式的数据
    $mapper->undefinedPropertyHandler = function($object, $key, $jvalue) {
        $object->$key = $jvalue;
    };
    $order = $mapper->map($obj, new Order());
    print_r($order);
    
    // App\Order Object
    // (
    //     [order_id] => 12345
    //     [goods] => Array
    //         (
    //             [0] => App\Goods Object
    //                 (
    //                     [name] => goods1
    //                     [price] => 12.1
    //                     [color] => red
    //                 )
    //             [1] => App\Goods Object
    //                 (
    //                     [name] => goods2
    //                     [price] => 12.99
    //                     [color] => yellow
    //                 )
    //         )
    // )