【问题标题】:How to deserialize a nested array of objects declared on the constructor via promoted properties, with Symfony Serializer?如何使用 Symfony Serializer 反序列化通过提升属性在构造函数上声明的嵌套对象数组?
【发布时间】:2021-12-23 22:29:06
【问题描述】:

参加以下 DTO 课程:

class UserDTO {
    /**
     * @param AddressDTO[] $addressBook
     */
    public function __construct(
        public string $name,
        public int $age,
        public ?AddressDTO $billingAddress,
        public ?AddressDTO $shippingAddress,
        public array $addressBook,
    ) {
    }
}

class AddressDTO {
    public function __construct(
        public string $street,
        public string $city,
    ) {
    }
}

我想对 JSON 进行序列化和反序列化。

我正在使用以下序列化程序配置:

$encoders = [new JsonEncoder()];

$extractor = new PropertyInfoExtractor([], [
    new PhpDocExtractor(),
    new ReflectionExtractor(),
]);

$normalizers = [
    new ObjectNormalizer(null, null, null, $extractor),
    new ArrayDenormalizer(),
];

$serializer = new Serializer($normalizers, $encoders);

但是当序列化/反序列化这个对象时:

$address = new AddressDTO('Rue Paradis', 'Marseille');
$user = new UserDTO('John', 25, $address, null, [$address]);

$jsonContent = $serializer->serialize($user, 'json');
dd($serializer->deserialize($jsonContent, UserDTO::class, 'json'));

我得到以下结果:

UserDTO^ {#54
  +name: "John"
  +age: 25
  +billingAddress: AddressDTO^ {#48
    +street: "Rue Paradis"
    +city: "Marseille"
  }
  +shippingAddress: null
  +addressBook: array:1 [
    0 => array:2 [
      "street" => "Rue Paradis"
      "city" => "Marseille"
    ]
  ]
}

当我期望时:

UserDTO^ {#54
  +name: "John"
  +age: 25
  +billingAddress: AddressDTO^ {#48
    +street: "Rue Paradis"
    +city: "Marseille"
  }
  +shippingAddress: null
  +addressBook: array:1 [
    0 => AddressDTO^ {#48
      +street: "Rue Paradis"
      +city: "Marseille"
    }
  ]
}

如您所见,$addressBook 被反序列化为array 的数组,而不是AddressDTO 的数组。 我希望 PhpDocExtractor 从构造函数中读取 @param AddressDTO[],但这不起作用。

仅当我将 $addressBook 设为使用 @var 记录的公共属性时才有效。

有没有办法让它在构造函数上使用简单的@param

(非)工作演示:https://phpsandbox.io/n/gentle-mountain-mmod-rnmqd


我读过和尝试过的:

建议的解决方案似乎都不适合我。

【问题讨论】:

  • 看起来不错。你在一个只注入 serializerInterface 的 symfony 应用程序中尝试过吗?
  • @alessandro_podo 我正在独立使用它。如果你想摆弄它,我在我的问题中添加了一个 phpsandbox.io 链接。

标签: symfony serialization symfony5


【解决方案1】:

显然问题在于PhpDocExtractor 没有从构造函数中提取属性。您需要为此使用特定的提取器:

use Symfony\Component\PropertyInfo;
use Symfony\Component\Serializer;

$phpDocExtractor = new PropertyInfo\Extractor\PhpDocExtractor();
$typeExtractor   = new PropertyInfo\PropertyInfoExtractor(
    typeExtractors: [ new PropertyInfo\Extractor\ConstructorExtractor([$phpDocExtractor]), $phpDocExtractor,]
);

$serializer = new Serializer\Serializer(
    normalizers: [
                    new Serializer\Normalizer\ObjectNormalizer(propertyTypeExtractor: $typeExtractor),
                    new Serializer\Normalizer\ArrayDenormalizer(),
                 ],
    encoders:    ['json' => new Serializer\Encoder\JsonEncoder()]
);

这样您将获得所需的结果。我花了一点时间才弄清楚。多个反规范化器/提取器链总是让我着迷。


或者,对于更复杂的操作系统特殊情况,您可以创建自己的自定义反规范化器:

use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait

class UserDenormalizer
    implements DenormalizerInterface, DenormalizerAwareInterface
{

    use DenormalizerAwareTrait;

    public function denormalize($data, string $type, string $format = null, array $context = [])
    {
        $addressBook = array_map(fn($address) => $this->denormalizer->denormalize($address, AddressDTO::class), $data['addressBook']);

        return new UserDTO(
            name:            $data['name'],
            age:             $data['age'],
            billingAddress:  $this->denormalizer->denormalize($data['billingAddress'], AddressDTO::class),
            shippingAddress: $this->denormalizer->denormalize($data['shippingAddress'], AddressDTO::class),
            addressBook:     $addressBook
        );
    }

    public function supportsDenormalization($data, string $type, string $format = null)
    {
        return $type === UserDTO::class;
    }
}

设置会变成这样:

$extractor = new PropertyInfoExtractor([], [
    new PhpDocExtractor(),
    new ReflectionExtractor(),
    
]);

$userDenormalizer = new UserDenormalizer();
$normalizers      = [
    $userDenormalizer,
    new ObjectNormalizer(null, null, null, $extractor),
    new ArrayDenormalizer(),

];
$serializer       = new Serializer($normalizers, [new JsonEncoder()]);
$userDenormalizer->setDenormalizer($serializer);

输出变成你所期望的:

^ UserDTO^ {#39
  +name: "John"
  +age: 25
  +billingAddress: AddressDTO^ {#45
    +street: "Rue Paradis"
    +city: "Marseille"
  }
  +shippingAddress: null
  +addressBook: array:2 [
    0 => AddressDTO^ {#46
      +street: "Rue Paradis"
      +city: "Marseille"
    }
  ]
}

【讨论】:

    猜你喜欢
    • 2018-04-26
    • 2017-08-28
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    • 2018-09-21
    • 2018-08-16
    • 1970-01-01
    相关资源
    最近更新 更多