Symfony 6中如何将DateTime数组反序列化为DateTime对象?
解决Symfony 6中Historique对象反序列化问题的方案
问题根源
你的createdAt字段是数组格式(包含date、timezone_type、timezone),但默认的DateTimeNormalizer只处理字符串格式的日期,而ObjectNormalizer无法自动将这种数组转换为DateTime对象,导致两种顺序的配置都报错。
可行方案
方案一:使用CallbackNormalizer预处理日期数组
通过CallbackNormalizer在反序列化前将createdAt数组手动转换为DateTime对象,再交给后续归一化器处理:
use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Normalizer\CallbackNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; $normalizers = [ new CallbackNormalizer( // 反序列化前的预处理回调 function ($data, $type, $format, $context) { if ($type === Historique::class && isset($data['createdAt'])) { // 将数组格式的日期转换为DateTime对象 $data['createdAt'] = \DateTime::createFromFormat( 'Y-m-d H:i:s.u', // 匹配带微秒的日期格式 $data['createdAt']['date'], new \DateTimeZone($data['createdAt']['timezone']) ); } return $data; } ), new ObjectNormalizer(null, null, null, new ReflectionExtractor()), new BackedEnumNormalizer(), new DateTimeNormalizer(), // 保留以处理其他字符串格式的日期 ]; $serializer = new Serializer($normalizers, [new JsonEncoder()]); // 执行反序列化 $historiqueObject = $serializer->denormalize($historiqueArray, Historique::class);
方案二:使用注解回调处理日期字段
在Historique类中添加反序列化回调注解,指定createdAt字段的转换逻辑:
- 给
Historique类添加注解和回调方法:
use Symfony\Component\Serializer\Annotation\DenormalizeCallback; class Historique { #[DenormalizeCallback(callback: [self::class, 'denormalizeCreatedAt'])] public \DateTime $createdAt; public function __construct(public readonly string $utilisateur, public readonly string $type, public readonly ?string $texte) { $this->createdAt = new \DateTime(); } // 静态回调方法:将数组转为DateTime public static function denormalizeCreatedAt(array $dateData): \DateTime { return \DateTime::createFromFormat( 'Y-m-d H:i:s.u', $dateData['date'], new \DateTimeZone($dateData['timezone']) ); } public function getTypeLabel(): string { return HistoriqueTypeEnum::getLabelName($this->type); } }
- 配置序列化器时添加
AnnotationNormalizer:
use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Normalizer\AnnotationNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; $normalizers = [ new AnnotationNormalizer(), // 优先处理注解回调 new DateTimeNormalizer(), new ObjectNormalizer(null, null, null, new ReflectionExtractor()), new BackedEnumNormalizer(), ]; $serializer = new Serializer($normalizers, [new JsonEncoder()]); $historiqueObject = $serializer->denormalize($historiqueArray, Historique::class);
关键注意点
- 日期格式要匹配输入的
date字段:输入包含微秒(.209330),所以必须用Y-m-d H:i:s.u格式,不能用你之前的Y-m-d H:i:s,否则会解析失败。 - 归一化器顺序很重要:处理日期转换的逻辑(
CallbackNormalizer或AnnotationNormalizer)必须放在ObjectNormalizer前面,确保createdAt在被赋值给对象前已经转换为DateTime对象。
内容的提问来源于stack exchange,提问作者eldiablo62
相关产品推荐
相关产品推荐

