如何将 JSON 字符串数组安全转换为 List(支持驼峰键映射)

来源:微信编程作者:马来西亚程序员头衔:程序员
导读:本期聚焦于马来西亚程序员创作的《如何将 JSON 字符串数组安全转换为 List(支持驼峰键映射)》,敬请观看详情。把一段 JSON 字符串数组转成 List 是后端开发里高频操作,但真正做起来坑不少:直接用 fastjson 或 Jackson 的 parseArray,遇到下划线键名就映射失败,字段悄悄变成 null;泛型擦除导致拿到的其实是 List 而不是 List,强转时直接抛 ClassCastException;数字精度、日期格式、未知字段也常常引发意外行为。本文围绕 Jackson 的 TypeReference、fastjson 的 TypeReference 以及手写反射映射三种主流方案展开,详细讲解如何配置 PropertyNamingStrategy 实现下划线转驼峰、如何用 @JsonProperty 注解精确控制字段映射、如何封装一个通用的安全转换工具类,并附上边界情况处理与性能对比建议,帮你彻底避开 JSON 转 List 过程中的常见陷阱。

在前后端交互和第三方接口对接中,把一段 JSON 字符串数组转换成 List 集合几乎是每天都会遇到的场景。看起来只是调用一行 API 的事情,实际隐藏着不少坑:键名风格不一致导致字段全为 null、泛型擦除导致强转报错、数字精度丢失等等。本文以最常用的 Jackson 和 fastjson 为例,讲清楚如何安全地把 JSON 字符串数组转成 List,并重点解决下划线键名到驼峰属性的映射问题。

如何将 JSON 字符串数组安全转换为 List(支持驼峰键映射)

一、先理解泛型擦除带来的坑

很多初学者会写出这样的代码:

ObjectMapper mapper = new ObjectMapper();
List<User> users = (List<User>) mapper.readValue(json, List.class);

编译能通过,运行时一用 users.get(0).getName() 就抛 ClassCastException。原因在于 readValue(json, List.class) 只知道要生成一个 List,不知道里面的元素该映射成什么类型,Jackson 会把每个 JSON 对象反序列化成 LinkedHashMap 塞进 List。此时 List 的实际泛型是 List<LinkedHashMap>,强转成 List<User> 在编译期被擦除检查放过,运行期必然报错。

正确的做法是显式告诉反序列化器完整的泛型信息。Jackson 提供了 TypeReference,fastjson 也提供了同名工具,两者用法类似:

ObjectMapper mapper = new ObjectMapper();
List<User> users = mapper.readValue(
        jsonArrayString,
        new TypeReference<List<User>>() {}
);

这里的匿名内部类 new TypeReference<List<User>>() {} 会把泛型签名保留在字节码里,框架通过反射读取这个签名就能还原出完整的 List<User> 结构。如果目标类型是运行时才确定的,还可以用 JavaType 动态构造:

JavaType javaType = mapper.getTypeFactory()
        .constructCollectionType(List.class, User.class);
List<User> users = mapper.readValue(jsonArrayString, javaType);

二、下划线键名如何映射到驼峰属性

对接第三方接口时,返回的 JSON 经常是下划线风格,比如 {"user_name":"张三","create_time":"2024-01-01"},而 Java 实体遵循驼峰命名 userNamecreateTime。如果不做任何配置,Jackson 默认按字段名精确匹配,找不到就忽略,转换出来的对象字段全是 null,而且不会报任何错误,这种静默失败在排查问题时非常隐蔽。

解决方式有两种。第一种是全局配置命名策略,让框架自动做下划线与驼峰的互转:

ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);

注意这个策略是双向的:反序列化时把 user_name 映射到 userName,序列化时也会把 userName 输出成 user_name。如果只需要在某个实体上生效,更推荐第二种方式,使用注解精确控制:

public class User {
    @JsonProperty("user_name")
    private String userName;

    @JsonProperty("create_time")
    private Date createTime;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;

    // 省略 getter/setter
}

注解方式的优点是不影响全局行为,多个不同风格的接口可以共存;缺点是字段多时写起来繁琐。实际项目中建议:自己系统内部的接口用全局策略,对接外部接口的 DTO 用注解,两者互不干扰。fastjson 用户对应的做法是在字段上加 @JSONField(name = "user_name"),或在解析时用 Feature.SupportSmartMatch,思路一致。

三、封装一个安全的转换工具类

直接在业务代码里 new ObjectMapper 既浪费对象开销,也可能因为各处配置不一致带来行为差异。建议把转换逻辑收敛到一个工具类中,统一配置、统一异常处理:

public class JsonUtils {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    static {
        // 反序列化时忽略未知字段,提升兼容性
        MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 空字符串转 null,避免抛错
        MAPPER.coercionConfigFor(String.class)
             .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull);
    }

    public static <T> List<T> parseList(String json, Class<T> clazz) {
        if (json == null || json.isBlank()) {
            return Collections.emptyList();
        }
        try {
            CollectionType type = MAPPER.getTypeFactory()
                    .constructCollectionType(List.class, clazz);
            return MAPPER.readValue(json, type);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("JSON 转 List 失败: " + e.getOriginalMessage(), e);
        }
    }
}

这个工具类有几个细节值得注意。第一,ObjectMapper 是线程安全的,声明为静态常量全局复用即可,不需要每次创建。第二,FAIL_ON_UNKNOWN_PROPERTIES 关闭后,JSON 里出现实体类没有的字段不会报错,这对接口字段演进的兼容性很重要。第三,入参判空直接返回空集合而不是 null,调用方拿到后可以直接遍历,避免空指针。

调用方式非常简单:

String json = "[{\"user_name\":\"张三\",\"create_time\":\"2024-01-01 12:00:00\"}]";
List<User> users = JsonUtils.parseList(json, User.class);
users.forEach(u -> System.out.println(u.getUserName()));

四、边界情况与常见误区

除了映射问题,还有几个容易踩的点需要提前考虑。其一是日期格式,Jackson 默认只认时间戳和 ISO 格式,遇到 2024-01-01 12:00:00 这种格式要加 @JsonFormat 或全局注册 JavaTimeModule 并设置日期格式。其二是数字精度,JSON 里超过 Long 范围的整数如果映射到 Double 会丢精度,金融场景建议字段直接用 BigDecimal 接收。其三是元素类型不一致,比如数组里混了字符串和对象,可以在转换前用 JsonNode 先校验一遍结构:

JsonNode root = MAPPER.readTree(json);
if (!root.isArray()) {
    throw new IllegalArgumentException("入参不是 JSON 数组");
}
for (JsonNode node : root) {
    if (!node.isObject()) {
        throw new IllegalArgumentException("数组中存在非对象元素");
    }
}

最后是框架选择问题。fastjson 的 parseArray 用法更简短,但历史上出现过多个高危漏洞版本,如果使用请务必升级到 fastjson2 或确认版本已修复已知问题;Jackson 功能更完整、社区更活跃,是 Spring Boot 的默认选择。无论选哪个,核心思路都一样:显式传递泛型信息、明确键名映射策略、关闭过度严格的默认校验、对异常输入做兜底处理。把这四点做到位,JSON 字符串数组转 List 这件事就真正安全了。

JSON转换List驼峰映射Jackson修改时间:2026-09-08 18:42:54

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260908/52916.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。