ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JSON核心三剑客:parseArray、parseObject与toJSONString深度解析

JSON核心三剑客:parseArray、parseObject与toJSONString深度解析 1. JSON核心三剑客从字符串到对象的魔法转换如果你在任何一个现代软件开发项目中工作过无论是前端、后端还是移动端JSONJavaScript Object Notation几乎是你每天都要打交道的“老朋友”。它轻量、易读、跨语言是数据交换的事实标准。但光有JSON字符串还不够我们得能把它变成程序里能操作的对象也得能把对象变回字符串去传输或存储。这就引出了处理JSON时最核心、最高频的三个操作parseArray、parseObject和toJSONString。别看名字简单里面门道可不少。用好了代码清晰高效用岔了可能就是深夜调试的崩溃源头。今天我们就抛开那些框架自带的注解和包装深入聊聊这三个方法的本质、使用场景以及那些官方文档里不会写的“坑”和技巧。简单来说这三个方法构成了JSON处理的“输入”和“输出”闭环。toJSONString负责“输出”将内存中的Java对象或Map、List等序列化成一个符合JSON格式的字符串。parseObject和parseArray则负责“输入”将JSON格式的字符串反序列化回Java世界里的对象或集合。它们通常由诸如Jackson、Gson、Fastjson、阿里巴巴的Fastjson2等库提供。虽然不同库的方法名可能略有差异例如Jackson里是readValue和writeValueAsString但核心思想完全一致。我们接下来的讨论将以最常见的场景和概念为主辅以具体库的示例力求让你掌握其精髓。2. 方法深度解析与核心原理2.1 JSON.toJSONString()对象到字符串的旅程toJSONString()方法的作用是将一个Java对象转换为JSON格式的字符串。这个过程叫做“序列化”或“编码”。听起来简单但对象内部可能包含各种复杂结构嵌套对象、集合、数组、日期、枚举甚至可能有循环引用。这个方法需要智能地处理所有这些情况。核心工作原理方法会通过反射Reflection遍历目标对象的所有可访问字段包括私有字段取决于库的配置和安全性设置。对于每个字段它会获取字段名和字段值。然后根据值的类型决定如何在JSON中表示基本类型int, boolean等及其包装类直接转换为对应的JSON值数字、布尔值。String类型转换为被双引号包裹的JSON字符串。集合List, Set或数组转换为JSON数组[...]。另一个对象递归调用序列化过程生成JSON对象{...}。null值转换为JSON的null。日期等特殊类型通常需要指定格式如yyyy-MM-dd HH:mm:ss否则库可能将其转换为时间戳长整型或其它默认格式。一个基础示例假设我们有一个简单的User对象。public class User { private String name; private Integer age; private ListString hobbies; // 构造方法、getter、setter省略 }使用Fastjson进行序列化User user new User(); user.setName(张三); user.setAge(25); user.setHobbies(Arrays.asList(阅读, 游泳, 编程)); String jsonString JSON.toJSONString(user); System.out.println(jsonString);输出结果类似于{age:25,hobbies:[阅读,游泳,编程],name:张三}注意字段的输出顺序age,hobbies,name可能与类中定义的顺序不同。JSON标准本身不要求对象键值对的顺序大多数JSON库为了性能使用HashMap其遍历顺序是不确定的。如果对顺序有严格要求例如某些需要签名验证的API需要使用LinkedHashMap或库提供的特性如Fastjson的SerializerFeature.MapSortField。高级特性与配置这才是体现功力的地方。单纯的默认转换往往不能满足生产需求。日期格式化这是最常见的坑。默认情况下Jackson可能将Date序列化为时间戳Fastjson可能输出为“yyyy-MM-dd HH:mm:ss”格式。务必显式指定格式。// Jackson示例 ObjectMapper mapper new ObjectMapper(); mapper.setDateFormat(new SimpleDateFormat(yyyy-MM-dd)); String json mapper.writeValueAsString(objWithDate); // Fastjson示例使用SerializerFeature String json JSON.toJSONString(objWithDate, SerializerFeature.WriteDateUseDateFormat); // 或者自定义格式 String json JSON.toJSONStringWithDateFormat(objWithDate, yyyy-MM-dd HH:mm:ss);忽略空值默认情况下所有字段都会被输出即使值为null。这会导致生成的JSON字符串冗长。可以通过配置忽略null值字段。// Fastjson String json JSON.toJSONString(obj, SerializerFeature.IgnoreNonFieldGetter, SerializerFeature.IgnoreErrorGetter); // 更常用的忽略null String json JSON.toJSONString(obj, SerializerFeature.SkipTransientField, SerializerFeature.IgnoreNonFieldGetter, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullListAsEmpty, SerializerFeature.WriteNullStringAsEmpty); // 注意WriteMapNullValue等特性是控制是否输出null组合使用需小心。 // Jackson更清晰 ObjectMapper mapper new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); String json mapper.writeValueAsString(obj);自定义序列化器Custom Serializer对于极其特殊的类型如一个复杂的权限对象你只想输出它的ID可以注册自定义的序列化器完全控制其转换为JSON的过程。循环引用问题如果对象A引用BB又引用A默认序列化会陷入死循环导致栈溢出。一些库如Jackson可以通过JsonIdentityInfo注解或配置来处理输出引用ID。而Fastjson默认会检测并避免循环引用通过SerializerFeature.DisableCircularReferenceDetect可以关闭但行为需要仔细测试。实操心得性能考量在超高并发或处理超大对象时序列化的性能至关重要。Fastjson以速度闻名但在复杂对象和特定场景下可能不如Jackson稳定。Jackson的ObjectMapper是线程安全的建议作为单例重用避免重复创建的开销。敏感信息过滤千万不要直接把ORM框架如MyBatis查出的实体对象用toJSONString()输出到日志或API响应中。实体对象可能包含密码、手机号等敏感字段即使你不想输出如果字段有getter方法默认序列化可能会包含它们。务必使用专用的DTOData Transfer Object或VOView Object来承载需要对外暴露的数据或者在字段上使用JsonIgnore注解。美化输出Pretty Print调试时一个格式化的JSON字符串非常有用。可以使用SerializerFeature.PrettyFormatFastjson或mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj)Jackson。2.2 JSON.parseObject()字符串到对象的精准映射parseObject()方法用于将JSON字符串反序列化为一个指定的Java类对象。这个过程是toJSONString()的逆过程称为“反序列化”或“解码”。它的挑战在于需要将灵活、无类型的JSON数据严格地映射到有严格类型定义的Java类的字段上。核心工作原理解析JSON结构首先库会解析JSON字符串构建一个内存中的树状结构如Jackson的JsonNodeFastjson内部的JSONObject。类型匹配与映射然后根据你提供的目标类如User.class通过反射获取类的所有字段及其类型信息。字段赋值遍历类的字段根据字段名或通过注解指定的别名去JSON树中查找对应的值。找到后尝试将该JSON值转换为字段声明的Java类型如将JSON数字转为int将JSON字符串转为String将JSON数组转为List等。对象构建通常通过调用目标类的无参构造方法创建实例然后将转换后的值通过setter方法或直接字段注入取决于配置设置进去。基础示例String jsonString {\name\:\李四\,\age\:30,\hobbies\:[\旅游\,\摄影\]}; User user JSON.parseObject(jsonString, User.class); System.out.println(user.getName()); // 输出李四 System.out.println(user.getHobbies()); // 输出[旅游, 摄影]关键细节与复杂场景处理字段名映射JSON的key和Java字段名默认需要完全匹配大小写敏感。如果不匹配可以使用注解。Jackson:JsonProperty(json_key_name)Fastjson:JSONField(name json_key_name)Gson:SerializedName(json_key_name)未知属性与严格模式如果JSON字符串中包含目标类没有的字段默认行为是什么Jackson默认会忽略FAIL_ON_UNKNOWN_PROPERTIES false而早期版本的Fastjson可能会出错。建议根据业务需要明确配置。对于严格的API解析建议开启失败模式避免因接口变动或拼写错误导致数据静默丢失。// Jackson 严格模式 ObjectMapper mapper new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); // 这样遇到未知属性会抛出 JsonMappingException嵌套对象和泛型集合这是parseObject的强大之处可以处理复杂结构。public class Order { private String orderId; private User buyer; // 嵌套User对象 private ListOrderItem items; // 泛型集合 // ... getters and setters } public class OrderItem { private String productName; private BigDecimal price; // ... getters and setters } String complexJson ...; // 包含嵌套User和OrderItem数组的JSON Order order JSON.parseObject(complexJson, Order.class); // 库会自动递归地创建User对象和ListOrderItem集合。对于泛型集合由于Java运行时类型擦除有时需要借助TypeReference来传递完整的泛型信息。// 解析一个直接是ListUser的JSON字符串 String jsonArrayString [{\name\:\王五\}, {\name\:\赵六\}]; // 错误方式ListUser list JSON.parseObject(jsonArrayString, List.class); // 这样list里的元素是JSONObject不是User // 正确方式使用TypeReference ListUser userList JSON.parseObject(jsonArrayString, new TypeReferenceListUser() {});日期反序列化和序列化一样日期是重灾区。必须确保反序列化时的日期格式与JSON字符串中的格式匹配或者使用库可以理解的默认格式如时间戳。// Jackson 配置日期格式 ObjectMapper mapper new ObjectMapper(); mapper.setDateFormat(new SimpleDateFormat(yyyy/MM/dd)); User user mapper.readValue(jsonStringWithDate, User.class);空值和默认值如果JSON中某个字段缺失或为null对应的Java字段会被设置成什么对于对象类型会是null对于基本类型如int则会因为无法设置null而抛出异常。解决方法是使用包装类Integer或者在类中为基本类型字段设置默认值。避坑指南永远不要信任外部输入传递给parseObject的JSON字符串可能来自不可信的来源如用户输入、外部API。恶意构造的超长字符串、深度嵌套的JSON如{a:{a:{a:...}}}可能导致栈溢出StackOverflowError或内存耗尽OutOfMemoryError。在生产环境中必须对输入的JSON大小进行限制并考虑使用安全模式。// Fastjson 1.2.68 支持安全模式限制深度、长度等 ParserConfig.getGlobalInstance().setMaxNestingDepth(100); // 或者使用带Feature参数的接口 JSON.parseObject(jsonStr, User.class, Feature.SafeMode);注意自动类型转换一些库如Fastjson在类型不严格匹配时会尝试自动转换例如JSON字符串123可以转成Integer123数字123可以转成字符串123。这有时很方便但有时会掩盖数据格式问题。Jackson默认更严格。构造函数与Setter反序列化通常要求类有一个无参构造方法或通过注解指定构造方法。赋值优先通过setter方法如果没有setter可能会尝试直接字段注入取决于库和配置。确保你的类设计符合库的预期。2.3 JSON.parseArray()构建有序的对象集合parseArray()方法是parseObject()在处理数组情况下的特化和便捷方法。它的目标很明确将一个JSON数组字符串格式为[...]反序列化为一个JavaList集合。核心工作原理其内部机制与parseObject解析数组部分高度一致。它先解析整个JSON字符串为一个数组结构然后遍历数组中的每一个元素。对于每个元素都是一个JSON对象调用与parseObject类似的逻辑将其转换为指定的Java类对象最后将所有对象添加到一个List中并返回。基础示例String jsonArrayString [{\name\:\Alice\, \age\:28}, {\name\:\Bob\, \age\:35}]; ListUser userList JSON.parseArray(jsonArrayString, User.class); for (User u : userList) { System.out.println(u.getName()); } // 输出 // Alice // Bob与parseObject解析泛型List的对比如前所述你也可以用parseObject配合TypeReference来解析JSON数组。那么parseArray存在的意义是什么API更简洁直观parseArray直接表达了“解析成数组/列表”的意图代码更易读。实现可能优化对于一些库parseArray可能针对数组场景做了一些特定的性能优化。返回类型明确parseArray直接返回ListT而parseObject需要绕一下TypeReference。本质上JSON.parseArray(jsonStr, User.class)可以看作是JSON.parseObject(jsonStr, new TypeReferenceListUser() {})的语法糖。在Fastjson中它们的底层实现是相通的。复杂元素类型的处理parseArray不仅能处理元素是简单POJO的数组也能处理元素是基础类型或其他复杂类型的数组。// 解析为 ListString String strArrayJson [\apple\, \banana\, \orange\]; ListString fruits JSON.parseArray(strArrayJson, String.class); // 解析为 ListInteger String numArrayJson [1, 2, 3, 4, 5]; ListInteger numbers JSON.parseArray(numArrayJson, Integer.class); // 解析为 ListMap String complexArrayJson [{\k1\:\v1\}, {\k2\:\v2\}]; ListMap mapList JSON.parseArray(complexArrayJson, Map.class); // 注意元素类型是Map当元素类型是Map.class时每个JSON数组元素必须是JSON对象会被转换为一个MapString, Object。这在处理动态结构或不方便定义POJO的JSON时非常有用。注意事项空数组与null如果JSON字符串是[]会返回一个空的List。如果JSON字符串是null则返回null。调用方需要做好空值判断。性能考虑对于非常大的JSON数组一次性解析到内存中的List可能会消耗大量内存。可以考虑使用流式解析Streaming API如Jackson的JsonParser以迭代的方式逐个读取数组元素这在处理日志文件或大数据流时至关重要。元素类型一致性确保JSON数组内的所有元素结构基本一致能够被成功转换为指定的目标类。如果元素结构差异过大解析可能会失败或产生意想不到的结果。3. 实战应用场景与代码示例理解了原理我们来看看它们在实际开发中如何组合使用解决具体问题。3.1 场景一构建RESTful API这是最经典的应用。后端接收前端传来的JSON反序列化处理后再将结果以JSON返回序列化。控制器Controller层示例使用Spring Boot JacksonRestController RequestMapping(/api/users) public class UserController { PostMapping public ResponseEntityUserDTO createUser(RequestBody UserCreateRequest request) { // 1. RequestBody 注解让Spring MVC自动调用Jackson的readValue(即parseObject) // 将HTTP请求体中的JSON字符串反序列化为UserCreateRequest对象。 // 2. 进行业务处理如数据校验、保存到数据库等。 User savedUser userService.createUser(request); // 3. 将保存后的实体对象转换为面向API的DTO对象。 UserDTO dto convertToDTO(savedUser); // 4. Spring MVC会自动调用Jackson的writeValueAsString(即toJSONString) // 将UserDTO对象序列化为JSON字符串放入HTTP响应体。 return ResponseEntity.ok(dto); } GetMapping public ResponseEntityListUserDTO getAllUsers() { ListUser users userService.getAllUsers(); ListUserDTO dtoList users.stream().map(this::convertToDTO).collect(Collectors.toList()); // 返回ListSpring MVC会将其序列化为JSON数组。 return ResponseEntity.ok(dtoList); } }在这个场景中parseObject和toJSONString的过程由框架在幕后完成开发者只需关注对象本身。但理解这个过程对于调试如日期格式不对、字段映射失败至关重要。3.2 场景二配置文件解析许多应用使用JSON作为配置文件格式如application.json因为它比Properties文件更结构化。public class AppConfig { private String appName; private DatabaseConfig database; private ListString allowedHosts; // ... getters and setters } public class DatabaseConfig { private String url; private String username; private String password; private int poolSize; // ... getters and setters }读取配置的代码import java.nio.file.Files; import java.nio.file.Paths; public class ConfigLoader { public static AppConfig loadConfig(String filePath) throws Exception { // 读取JSON配置文件内容 String content new String(Files.readAllBytes(Paths.get(filePath))); // 使用parseObject解析复杂的嵌套结构 AppConfig config JSON.parseObject(content, AppConfig.class); // 可以进行后续验证例如检查database.poolSize是否大于0 if (config.getDatabase() ! null config.getDatabase().getPoolSize() 0) { throw new IllegalArgumentException(数据库连接池大小必须为正数); } return config; } }配置文件config.json:{ appName: 我的应用, database: { url: jdbc:mysql://localhost:3306/mydb, username: root, password: secret, poolSize: 10 }, allowedHosts: [127.0.0.1, localhost, 192.168.1.100] }这里parseObject的强大嵌套解析能力得到了完美体现。3.3 场景三缓存数据序列化将对象序列化为JSON字符串后存入Redis或Memcached等缓存系统是常见的做法。Service public class UserCacheService { Autowired private RedisTemplateString, String redisTemplate; private static final String USER_KEY_PREFIX user:; public User getUserById(Long userId) { String key USER_KEY_PREFIX userId; String cachedJson redisTemplate.opsForValue().get(key); if (cachedJson ! null !cachedJson.isEmpty()) { // 缓存命中使用parseObject反序列化 return JSON.parseObject(cachedJson, User.class); } // 缓存未命中从数据库查询 User user userRepository.findById(userId).orElse(null); if (user ! null) { // 将对象序列化为JSON字符串后存入缓存 String jsonToCache JSON.toJSONString(user); redisTemplate.opsForValue().set(key, jsonToCache, 1, TimeUnit.HOURS); // 缓存1小时 } return user; } public void updateUser(User user) { // 更新数据库 userRepository.save(user); // 使缓存失效或更新缓存 String key USER_KEY_PREFIX user.getId(); String updatedJson JSON.toJSONString(user); redisTemplate.opsForValue().set(key, updatedJson, 1, TimeUnit.HOURS); } }注意在缓存场景下要特别注意序列化/反序列化的性能。Fastjson通常有优势。另外确保被缓存的对象类结构稳定如果字段增减旧缓存的反序列化可能会失败。可以考虑添加版本号到key中如user:v1:123。3.4 场景四处理第三方API响应调用外部HTTP API对方返回JSON字符串我们需要将其解析为内部对象。Component public class WeatherService { Autowired private RestTemplate restTemplate; public WeatherInfo getWeatherByCity(String city) { String url https://api.weather.com/v3?city city; // 调用第三方API ResponseEntityString response restTemplate.getForEntity(url, String.class); String responseBody response.getBody(); // 第三方API的JSON结构可能非常复杂且字段名可能不符合Java命名规范。 // 方案1如果结构固定定义对应的DTO类并使用JsonProperty映射字段。 // WeatherInfo info JSON.parseObject(responseBody, WeatherInfo.class); // 方案2如果只需要其中几个字段或者结构多变可以先解析为通用的JsonNodeJackson或JSONObjectFastjson。 // 这里以Fastjson的JSONObject为例 JSONObject jsonObj JSON.parseObject(responseBody); // 注意这里用的是无目标类的parseObject返回JSONObject WeatherInfo info new WeatherInfo(); info.setCity(jsonObj.getString(location)); // 直接通过key获取值 info.setTemperature(jsonObj.getJSONObject(current).getDouble(temp_c)); // 嵌套获取 info.setCondition(jsonObj.getJSONObject(current).getJSONArray(condition).getJSONObject(0).getString(text)); // 更复杂的路径 // 方案3使用JsonPath如Jayway JsonPath进行查询更简洁。 // String condition JsonPath.read(responseBody, $.current.condition[0].text); return info; } }这个场景展示了parseObject的灵活性既可以强类型映射到POJO也可以弱类型解析为JSONObject进行动态操作。4. 性能优化、安全与最佳实践当这三个方法用在高并发、大数据量或安全敏感的环境中时一些细节决定了系统的稳定性和效率。4.1 性能优化要点重用解析器实例对于JacksonObjectMapper的创建成本很高且是线程安全的务必将其配置为单例如通过Spring的Bean注入。对于Fastjson虽然JSON类的静态方法内部有缓存机制但在极端性能要求下也可以考虑重用JSON.parseObject的特定配置实例通过JSON.parseObject(String, Type, ParserConfig, Feature...)。选择合适的库Fastjson序列化/反序列化速度极快API简单曾是很多互联网公司的首选。但历史上安全漏洞较多且某些自动类型转换行为可能带来隐患。如果使用务必升级到最新安全版本如Fastjson2。Jackson功能强大、稳定、社区活跃是Spring Boot的默认选择。性能优秀且高度可配置。对于大多数项目Jackson是更稳妥的选择。Gson由Google开发简单易用默认配置更严格如日期处理性能稍逊于前两者但足够用于大多数场景。避免不必要的序列化只在需要将对象转换为字符串用于网络传输、持久化、日志时才调用toJSONString。在内存中传递对象本身效率更高。使用流式API处理大JSON对于几百MB甚至GB级的JSON文件千万不要用parseObject或parseArray一次性加载到内存。应使用流式解析如Jackson的JsonParser或增量解析如Gson的JsonReader像读取XML的SAX解析器一样边读边处理。// Jackson Streaming API 示例 ObjectMapper mapper new ObjectMapper(); try (JsonParser parser mapper.getFactory().createParser(new File(huge.json))) { while (parser.nextToken() ! null) { String fieldName parser.getCurrentName(); // ... 根据字段名处理值 } }定制序列化/反序列化对于特别复杂的对象或需要特殊处理的字段使用自定义的JsonSerializer和JsonDeserializer。虽然增加了复杂度但可以避免在业务代码中做繁琐的转换有时也能提升性能。4.2 安全考量与漏洞防范JSON反序列化是安全重灾区攻击者可以通过精心构造的JSON字符串触发远程代码执行RCE等严重漏洞。Fastjson的AutoType漏洞这是Fastjson历史上最著名的问题。为了反序列化接口或抽象类Fastjson引入了type特性在JSON中指定类的全限定名。攻击者可以利用此特性让应用加载并实例化任意类如果该类有危险的构造方法或setter就可能执行恶意代码。防御措施升级到最新安全版本Fastjson2Fastjson2重写了代码默认关闭了AutoType支持安全性大幅提升。如果必须使用Fastjson 1.x务必使用安全模式并设置白名单。ParserConfig config new ParserConfig(); config.setSafeMode(true); // 开启安全模式 // 或者设置白名单 config.addAccept(com.yourcompany.safe.); // 然后使用带配置的parseObject JSON.parseObject(jsonStr, Object.class, config, Feature.SafeMode);Jackson的多态类型处理JsonTypeInfoJackson也支持多态反序列化通过JsonTypeInfo注解。如果配置不当同样存在类似AutoType的风险。切勿使用JsonTypeInfo.Id.CLASS或基于类名的机制来反序列化来自不可信源的JSON。应使用JsonTypeInfo.Id.NAME并配合明确的子类型映射。输入大小限制如前所述必须对输入的JSON字符串长度进行限制防止DoS攻击。反序列化到非预期类型确保parseObject的第二个参数目标类是你明确期望的类型。不要反序列化到通用的Object.class、Map.class或JSONObject后再进行复杂的类型推断和转换这增加了不确定性。4.3 日常开发最佳实践清单统一配置避免魔法字符串在项目中对于日期格式、空值处理、未知属性忽略等配置应该集中管理。例如在Spring Boot中可以通过配置一个全局的ObjectMapperBean。Configuration public class JacksonConfig { Bean public ObjectMapper objectMapper() { ObjectMapper mapper new ObjectMapper(); mapper.setDateFormat(new SimpleDateFormat(yyyy-MM-dd HH:mm:ss)); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 根据业务选择 mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 可能关闭某些不安全的特性 mapper.deactivateDefaultTyping(); // 禁用默认的多态类型处理 return mapper; } }使用DTO/VO进行层间数据传输永远不要直接将数据库实体Entity序列化后暴露给API。创建专用的数据传输对象只包含必要的字段并可以在此进行格式调整、数据脱敏等操作。编写单元测试为复杂的序列化/反序列化逻辑编写单元测试特别是针对边界情况如null、空字符串、超大数字、特殊字符、日期边界值和自定义序列化器/反序列化器。日志记录要谨慎在记录日志时如果要输出对象考虑使用toJSONString并配合忽略敏感字段的配置或者重写对象的toString()方法避免将密码、令牌、身份证号等敏感信息打印到日志中。处理异常parseObject和parseArray可能会抛出多种异常如JsonParseExceptionJSON格式错误、JsonMappingException字段映射失败、IllegalArgumentException等。务必进行捕获并转化为对用户或调用方友好的错误信息而不是直接将堆栈轨迹抛出去。5. 疑难杂症与排查实录即使遵循了最佳实践在实际开发中还是会遇到各种奇怪的问题。下面记录几个我踩过的坑和排查思路。5.1 日期字段一会儿是字符串一会儿是时间戳问题描述同一个Date字段在A接口返回的是2023-10-27在B接口返回的却是1698336000000导致前端解析失败。根因分析序列化该字段的ObjectMapper配置不一致。可能A接口使用了全局配置的ObjectMapper设置了日期格式而B接口在某个地方自己new了一个ObjectMapper使用默认配置将Date序列化为时间戳。解决方案确保整个项目使用统一配置的ObjectMapper单例。在Spring项目中通过Bean注入是推荐做法。如果确实需要不同的格式使用注解在字段级别指定。public class MyDto { JsonFormat(pattern yyyy-MM-dd) private Date myDate; // ... }排查代码找出哪里创建了新的、未配置的ObjectMapper实例。5.2 字段明明有值序列化后却没了问题描述一个User对象的private String nickname;字段在调试时有值但调用toJSONString()后生成的JSON里没有nickname这个属性。排查步骤检查Getter方法大多数JSON库默认通过getter方法访问字段值。确认是否有getNickname()方法方法名是否正确是getNickname而不是isNickname或getnickname方法是否是public的检查注解字段或getter方法上是否有JsonIgnore注解检查库的配置是否配置了忽略null值而该字段恰好为null但你说调试时有值所以可能不是这个原因。是否使用了错误的SerializerFeature组合检查字段修饰符Fastjson默认可以访问私有字段但Jackson可能需要配置mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)才能直接访问字段不通过getter。如果getter不存在且库配置为必须通过getter访问字段就会被忽略。最常见的原因就是缺少标准的getter方法。使用Lombok的Data注解可以自动生成但要注意Lombok生成的getter/setter方法名是否符合库的预期。5.3 解析JSON数组时泛型类型丢失List里面全是LinkedHashMap问题描述String json [{\name\:\test\}]; ListUser list JSON.parseObject(json, List.class); // 错误 // 此时 list.get(0) 的类型是 LinkedHashMap不是User。原因Java的泛型在运行时被擦除List.class在运行时就是原始的List类型库不知道你要把里面的元素转换成User。所以它就用最通用的Map如LinkedHashMap来表示JSON对象。解决方案使用TypeReferenceJackson/Fastjson通用模式ListUser list objectMapper.readValue(json, new TypeReferenceListUser() {}); // 或 Fastjson ListUser list JSON.parseObject(json, new TypeReferenceListUser() {});使用parseArrayFastjson便捷方式ListUser list JSON.parseArray(json, User.class);5.4 循环引用导致栈溢出StackOverflowError问题描述两个对象互相引用如Parent有一个ListChild每个Child又有一个Parent属性。在序列化时toJSONString()陷入无限递归最终抛出StackOverflowError。解决方案使用JsonIgnore忽略一方在Child的parent字段上添加JsonIgnore打断循环。这是最简单直接的方法适用于不需要在JSON中体现该关系的情况。使用JsonManagedReference和JsonBackReferenceJackson特有这是一对注解用于处理父子关系。在“主”方如Parent的children字段用JsonManagedReference在“从”方如Child的parent字段用JsonBackReference。序列化时JsonBackReference标注的属性会被忽略。使用JsonIdentityInfoJackson这个注解会给对象分配一个ID当第二次遇到同一个对象时只输出其ID而不是再次展开。JsonIdentityInfo(generator ObjectIdGenerators.PropertyGenerator.class, property id) public class Parent { private Long id; private ListChild children; //... } // Child类也需要加上同样的注解使用Fastjson的循环引用检测默认开启Fastjson默认会检测循环引用并在第二次遇到同一对象时用{$ref:$}这样的引用来表示。这可以避免栈溢出但生成的JSON可能不符合某些严格校验器的要求。可以通过SerializerFeature.DisableCircularReferenceDetect关闭此特性不推荐。5.5 数字精度丢失问题问题描述一个BigDecimal类型的字段值为123456789.123456789序列化再反序列化后变成了123456789.12345679最后一位丢失了。原因分析某些JSON库在序列化BigDecimal时可能会默认将其当作double处理而double是浮点数存在精度损失。或者在反序列化时JSON中的数字被先解析成了double再转换为BigDecimal。解决方案对于Jackson确保ObjectMapper针对BigDecimal使用JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN特性将其写作纯数字字符串不科学计数法并且反序列化时能正确识别。ObjectMapper mapper new ObjectMapper(); mapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN); // 同时反序列化时对于BigDecimal字段Jackson默认会正确处理字符串形式的数字。对于Fastjson在序列化时使用SerializerFeature.WriteBigDecimalAsPlain。String json JSON.toJSONString(objWithBigDecimal, SerializerFeature.WriteBigDecimalAsPlain);终极方案对于涉及金额、高精度计算等场景考虑在DTO中用String类型来传输这些数字在业务层再转换为BigDecimal完全避免JSON库的转换干扰。虽然麻烦但绝对安全。JSON的这三个核心方法就像木匠手中的锤子、锯子和尺子是最基础的工具。掌握它们的原理、特性和陷阱不仅能让你高效地完成日常工作更能让你在遇到复杂问题时能够快速定位、从容解决。记住在软件开发的领域里对基础工具的深刻理解永远是构建稳健系统的基石。
返回列表