Symfony 6.2 SerializedPath注解序列化数组至XML异常求助
问题原因
当使用#[SerializedPath('[payment][allowedMethods][include][@name]')]注解标记一维数组属性时,Symfony Serializer会遍历数组中的每个元素,尝试将值依次写入同一个路径[payment][allowedMethods][include][@name]。第一次写入后,该路径已经存在非空值,第二次遍历触发AbstractObjectNormalizer中的校验逻辑,抛出The element you are trying to set is already populated异常。
标量值能正常工作是因为只需要写入一次路径,不会触发重复赋值的校验;而数组多元素的场景下,重复操作同一路径就会触发异常。同时,XML序列化逻辑中,直接给属性(带@的路径)赋值数组不符合XML属性的单值特性,无法生成多个<include>节点。
直接解决方案:调整数组结构与SerializedPath
要生成目标XML结构,需要让数组每个元素对应一个独立的<include>节点,每个节点通过@name指定属性值。调整代码如下:
class Payment { #[SerializedPath('[payment][allowedMethods][include]')] public array $methods = [ ['@name' => 'Method A'], ['@name' => 'Method B'], ]; }
执行序列化后,会生成预期的XML:
<?xml version="1.0"?> <response> <payment> <allowedMethods> <include name="Method A"/> <include name="Method B"/> </allowedMethods> </payment> </response>
这里的核心是将一维数组改为二维数组,每个元素是包含@name键的关联数组,同时SerializedPath指向<allowedMethods>下的<include>节点集合,Serializer会自动将每个数组元素解析为独立的<include>节点,并将@name转换为节点的name属性。
替代方案:自定义Normalizer(保持原数组结构)
如果不想修改原数组的一维结构,可以自定义Normalizer,在序列化前将一维数组转换为符合XML要求的结构:
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; class PaymentMethodsNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface { use NormalizerAwareTrait; public function normalize(mixed $object, string $format = null, array $context = []): array { // 先调用默认序列化逻辑 $data = $this->normalizer->normalize($object, $format, $context); // 转换methods一维数组为XML需要的结构 if (isset($data['methods']) && is_array($data['methods'])) { $data['payment']['allowedMethods']['include'] = array_map(function($method) { return ['@name' => $method]; }, $data['methods']); unset($data['methods']); } return $data; } public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { // 仅对Payment类的XML序列化生效 return $data instanceof Payment && $format === 'xml'; } }
将该Normalizer注册到Symfony服务容器后(例如在config/services.yaml中添加配置),即可保持原Payment类的一维数组结构,同时生成正确的XML输出。
内容的提问来源于stack exchange,提问作者Nico Richter

