如何使用 Symfony Serializer 反序列化通过提升的属性在构造函数上声明的嵌套对象数组?
How to deserialize a nested array of objects declared on the constructor via promoted properties, with Symfony Serializer?
采用以下 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,
) {
}
}
我想对它们进行序列化和反序列化 to/from 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);
但是当serializing/deserializing这个对象时:
$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[]
中读取 @param AddressDTO[]
,但这不起作用。
只有当我将 $addressBook
设为 public 属性 并用 @var
记录时它才有效。
有没有办法让它在构造函数上使用简单的 @param
?
(非)工作演示:https://phpsandbox.io/n/gentle-mountain-mmod-rnmqd
我阅读和尝试过的内容:
- Extract types of constructor parameters from docblock comment
- symfony deserialize nested objects
- How can I deserialize an array of objects in Symfony Serializer?
None 的建议解决方案似乎对我有用。
显然问题是 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()]
);
有了这个,你会得到想要的结果。我花了一点时间才弄明白。多个 denormalizer/extractor 连锁店总能吸引我。
或者,对于更复杂的 os 特殊情况,您可以创建自己的自定义反规范化器:
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"
}
]
}
采用以下 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,
) {
}
}
我想对它们进行序列化和反序列化 to/from 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);
但是当serializing/deserializing这个对象时:
$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[]
中读取 @param AddressDTO[]
,但这不起作用。
只有当我将 $addressBook
设为 public 属性 并用 @var
记录时它才有效。
有没有办法让它在构造函数上使用简单的 @param
?
(非)工作演示:https://phpsandbox.io/n/gentle-mountain-mmod-rnmqd
我阅读和尝试过的内容:
- Extract types of constructor parameters from docblock comment
- symfony deserialize nested objects
- How can I deserialize an array of objects in Symfony Serializer?
None 的建议解决方案似乎对我有用。
显然问题是 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()]
);
有了这个,你会得到想要的结果。我花了一点时间才弄明白。多个 denormalizer/extractor 连锁店总能吸引我。
或者,对于更复杂的 os 特殊情况,您可以创建自己的自定义反规范化器:
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"
}
]
}