如何使 Symfony Serializer 的属性可以为空

Posted

技术标签:

【中文标题】如何使 Symfony Serializer 的属性可以为空【英文标题】:How to make property nullable for Symfony Serializer 【发布时间】:2022-01-03 05:12:40 【问题描述】:

我正在尝试反序列化为具有属性的对象,该属性可能将对象数组作为值或null

反序列化数组没有问题,但我需要将 null 反序列化为空数组或 null 本身。

例如 "items": null

class A 
    /**
     * @var null|Item[]
     */
    private $items = [];

    /**
     * @return Item[]|null
     */
    public function getItems(): ?array
    
        return $this->items ?? [];
    

    /** 
     * @param Item $param
     * @return A
     */
    public function addItem(Item $param)
    
        if (!is_array($this->items)) $this->items = [];
        if (!in_array($param, $this->items))
            $this->items[] = $param;
        return $this;
    

//    /** tried with this as well
//     * @param array|null $param
//     * @return A
//     */
//    public function setItems(?array $param)
//    
//        $this->items = $param ?? [];
//        return $this;
//    

    /**
     * @param Item $item
     * @return A
     */
    public function removeItem(Item $item): A
    
        if (!is_array($this->items)) $this->items = [];
        if (in_array($item, $this->items))
            unset($this->items[array_search($item, $this->items)]);
        return $this;
    

    /**
     * @param Item $item
     * @return bool
     */
    public function hasItem(Item $item): bool
    
        return in_array($item, $this->items);
    

序列化器看起来像这样

        $defaultContext = [
            AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER =>
                function ($articles, $format, $context) 
                    return $articles->getId();
                ,
            AbstractObjectNormalizer::SKIP_NULL_VALUES => false
        ];

        $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
        $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);

        $encoders = [new JsonEncoder()];

       $serializer = new Serializer([
            new ArrayDenormalizer(),
            new DateTimeNormalizer(),
            new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter, null,
                new ReflectionExtractor(), null, null, $defaultContext
            ),
        ], $encoders);

    $a = $serializer->deserialize(' "items": null ', A::class, 'json');

items 为 null 时出现的错误

  [Symfony\Component\Serializer\Exception\InvalidArgumentException]  
  Data expected to be an array, null given.        

是否可以有可为空的属性?

【问题讨论】:

你在这段代码的哪个地方实际反序列化? @DirkJ.Faber 添加了代码 你能把课堂上的private $items = [];改成private $items;,然后告诉我会发生什么吗? @DirkJ.Faber For "items": null 产生相同的异常,因为 "items": [] 导致App\Model\A -items: null 而不是App\Model\A -items: [] 【参考方案1】:

追查到 Serializer 源代码,发现了三个可能的选项来拥有一个可为空的数组。

选项 1

删除 addItemhasItemremoveItem 方法,它允许设置 null、数组等。在我的情况下,这是不太受欢迎的解决方案。

选项 2

添加构造函数也有帮助。 https://github.com/symfony/serializer/blob/5.3/Normalizer/AbstractNormalizer.php#L381

    /**
     * A constructor.
     * @param array|null $items
     */
    public function __construct($items)
    
        $this->items = $items ?? [];
    

选项 3

扩展ArrayDenormalizer 并覆盖denormalize 方法来处理空值

    public function denormalize($data, string $type, string $format = null, array $context = []): array
    
        if (null === $this->denormalizer) 
            throw new BadMethodCallException('Please set a denormalizer before calling denormalize()!');
        
        if (!\is_array($data) && !is_null($data)) 
            throw new InvalidArgumentException('Data expected to be an array or null, ' . get_debug_type($data) . ' given.');
        
        if (!str_ends_with($type, '[]')) 
            throw new InvalidArgumentException('Unsupported class: ' . $type);
        
        if(is_null($data))
            return [];

        $type = substr($type, 0, -2);

        $builtinType = isset($context['key_type']) ? $context['key_type']->getBuiltinType() : null;
        foreach ($data as $key => $value) 
            if (null !== $builtinType && !('is_' . $builtinType)($key)) 
                throw new NotNormalizableValueException(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, $builtinType, get_debug_type($key)));
            

            $data[$key] = $this->denormalizer->denormalize($value, $type, $format, $context);
        

        return $data;
    

【讨论】:

以上是关于如何使 Symfony Serializer 的属性可以为空的主要内容,如果未能解决你的问题,请参考以下文章

使用 Symfony 2 序列化器对对象中的嵌套结构进行非规范化

带有序列化器的 Symfony JsonResponse

Symfony2,如何使表单标签类/属性与​​其输入不同?

如何使 Symfony 2 资产编译以产生不同的文件名?

如何使用 Symfony2 使 form_rest() 不显示字段?

在JMS Serializer上反序列化期间构造对象