You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring反序列化YAML时数组转为LinkedHashMap而非ArrayList的问题

Spring YAML配置泛型Map反序列化异常问题解决

问题场景

当将以下YAML配置中的props反序列化为泛型Map时:

props:
 array:
 - name: foo1
   bar: bar1
 - name: foo2
   bar: bar2

出现不符合预期的行为:

  • props实例为LinkedHashMap
  • array被错误反序列化为LinkedHashMap,而非预期的List

但直接使用new ObjectMapper(new YAMLFactory())读取配置时,array会正确转为ArrayList;将属性映射到带有List类型array属性的自定义对象时,结果也符合预期。重写ObjectMapper Bean无法解决该问题,使用环境为Spring 5.3.21 + Java 17。

核心原因

Spring处理配置绑定(尤其是泛型类型)时,并非直接依赖Jackson的ObjectMapper,而是通过**Binder和ConversionService**体系完成。当目标类型是无具体泛型约束的Map时,Spring无法推断嵌套节点的容器类型,会默认用LinkedHashMap处理嵌套结构,这是设计行为而非Bug。而Jackson会直接根据YAML语法结构推断数组类型,因此二者行为存在差异。

解决方法

1. 自定义GenericConverter

实现GenericConverter,针对Map类型的转换逻辑做调整,识别出需要转为List的嵌套节点并处理:

import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.convert.TypeDescriptor;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

public class MapToListConverter implements GenericConverter {
    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
        return Set.of(new ConvertiblePair(Map.class, Map.class));
    }

    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        if (!(source instanceof Map<?, ?> sourceMap)) {
            return source;
        }

        Map<String, Object> resultMap = new LinkedHashMap<>(sourceMap.size());
        sourceMap.forEach((key, value) -> {
            // 可根据实际场景调整判断逻辑,比如判断值是否为嵌套Map且符合数组结构特征
            if ("array".equals(key) && value instanceof Map<?, ?> nestedMap) {
                resultMap.put((String) key, new ArrayList<>(nestedMap.values()));
            } else {
                resultMap.put((String) key, value);
            }
        });
        return resultMap;
    }
}

将转换器注册到Spring的ConversionService:

import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import javax.annotation.PostConstruct;

@Configuration
public class ConverterRegisterConfig {
    private final RequestMappingHandlerAdapter handlerAdapter;

    public ConverterRegisterConfig(RequestMappingHandlerAdapter handlerAdapter) {
        this.handlerAdapter = handlerAdapter;
    }

    @PostConstruct
    public void registerConverter() {
        ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) handlerAdapter.getWebBindingInitializer();
        if (initializer != null && initializer.getConversionService() instanceof GenericConversionService conversionService) {
            conversionService.addConverter(new MapToListConverter());
        }
    }
}

2. 使用@ConfigurationProperties绑定明确类型

定义带有明确泛型的配置类,让Spring能准确推断类型:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;

@Component
@ConfigurationProperties(prefix = "props")
public class PropsConfig {
    private List<Map<String, String>> array;

    // getter、setter方法
    public List<Map<String, String>> getArray() {
        return array;
    }

    public void setArray(List<Map<String, String>> array) {
        this.array = array;
    }
}

这种方式无需自定义转换器,Spring会根据配置类的字段类型正确反序列化array为List。

3. 自定义配置绑定逻辑(进阶)

若需要全局调整泛型Map的绑定行为,可以重写ConfigurationPropertiesBindingPostProcessor,替换默认的Binder配置,指定嵌套数组结构的默认容器类型为ArrayList。此方式复杂度较高,需熟悉Spring配置绑定的底层逻辑,谨慎使用。

内容的提问来源于stack exchange,提问作者user2668735

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 05:40:22