当前位置: 首页 > news >正文

从 “JSON 字段适配噩梦” 到 “Spring Boot 优雅解决方案”,你只差这一篇

在 Spring Boot 项目开发中,前后端数据交互时,JSON 数据格式凭借其简洁、高效的特性,成为了数据传输的 “宠儿”。但在 Spring Boot 项目日常开发中,一个让无数开发者头疼不已的问题常常出现:前端传来的 JSON 数据与后端 Java 类的属性对不上号 。想象一下,你满心欢喜地准备接收前端发送的数据,却发现它们像是两个世界的产物,无法完美契合。
比如,前端提交的 JSON 数据可能是这样的:

{"name": "lybgeek","mobile": "13800000000","extFields": {"email": "lybgeek@163.com","age": 18}
}

又或是这样:

{"name": "lybgeek","mobile": "13800000000","email": "lybgeek@163.com","age": 18
}

而后端定义的 Java 类却只有简单的这几个属性:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private String name;private String mobile;
}

这种情况下,后端如何精准地获取 JSON 中的email和age字段呢?难道只能望 “数据” 兴叹,束手无策?当然不是!今天,就为大家带来一场知识盛宴,详细剖析多种实用到爆的解决方案,让你轻松攻克这一难题,代码写得更丝滑,开发效率直线上升!

场景痛点剖析:为什么 JSON 字段匹配问题如此棘手?

在微服务架构盛行的当下,前后端分离开发模式成为主流。前端开发人员按照业务需求构建灵活的数据结构,而后端开发人员则依据自身的业务逻辑定义 Java 实体类。这就导致了在数据交互的过程中,出现 JSON 字段与 Java 类属性不匹配的情况。这种不匹配不仅增加了开发的复杂度,还可能引发数据解析错误、类型转换异常等问题,严重影响项目的稳定性和开发进度。

实战演练:获取 JSON 中 “多余字段” 的 N 种姿势

方法一:巧用 Map,轻松接招

通过定义一个Map对象来接收和处理这些额外字段,简单又直接,就像是给你的数据处理工具箱里添加了一把万能扳手,轻松应对各种不匹配情况。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private String name;private String mobile;protected Map<String,Object> extFields;
}

测试代码如下:

 @PostMapping("json-map")
public User getUser(@RequestBody User user){UserUtil.print(user,"email","age");return user;
}

打印工具类:

public final class UserUtil {private UserUtil() {}public static void print(User user String...key){System.out.println("name:" + user .getName());System.out.println("mobile:" + user .getMobile());if(ArrayUtil.isNotEmpty(key)){for (String k : key) {System.out.println(k + ":" + user.getExtFields().get(k));}}}}

单元测试:

 @Test@DisplayName("测试-通过Map处理JSON对象中未被Java类中属性映射的字段")public void testJsonMap() throws Exception{String json = """{"name": "lybgeek","mobile": "13800000000","extFields": {"email": "lybgeek@163.com","age": 18}}"""MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user/json-map").contentType(MediaType.APPLICATION_JSON_UTF8).content(json)).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();}

控制台输出:

name:lybgeek
mobile:13800000000
email:lybgeek@163.com
age:18

优势: 实现简单,无需引入额外的依赖,对于临时处理少量额外字段非常方便。
局限: 在复杂的业务场景下,对Map的操作可能会使代码变得不够直观,难以维护。

方法二:借助 JsonNode,精准出击

引入 Jackson 库,使用JsonNode来处理,就如同给你的数据处理装上了高精度的瞄准镜,精准定位并提取所需字段。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private String name;private String mobile;private JsonNode extFields;
}

测试代码:

 @PostMapping("json-node")public User getUser(@RequestBody User user){UserUtil.print(user,"email","age");return user;}

单元测试:

 @Test@DisplayName("测试-通过JsonNode处理JSON对象中未被Java类中属性映射的字段")public void testJsonNode() throws Exception{String json = """{"name": "lybgeek","mobile": "13800000000","extFields": {"email": "lybgeek@163.com","age": 18}}"""MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user/json-node").contentType(MediaType.APPLICATION_JSON_UTF8).content(json)).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();}

控制台输出:

name:lybgeek
mobile:13800000000
email:lybgeek@163.com
age:18

优势: 提供了灵活的 JSON 节点访问方式,能够处理复杂的 JSON 结构,对于嵌套的 JSON 数据处理得心应手。
局限: 需要对 Jackson 库有一定的了解,并且代码中对JsonNode的操作较多时,会增加代码的复杂性。

方法三:@JsonAnySetter 与 @JsonAnyGetter 双剑合璧

  • @JsonAnySetter 是 Jackson 库中的一个注解,用于处理 JSON 对象中未被 Java 类中属性映射的字段,当 JSON 数据包含不匹配类中定义的属性时,使用此注解的方法会接收这些额外的键值对。
  • @JsonAnyGetter 注解用于标记一个返回 Map<String, Object> 类型的方法,Jackson 在将 Java 对象序列化为 JSON 时,会将该方法返回的 Map 中的键值对添加到生成的 JSON 对象中,这样可以在不预先定义所有属性的情况下,动态地向 JSON 中添加属性。

使用 Jackson 库的@JsonAnySetter和@JsonAnyGetter注解,实现动态处理,就像给你的数据处理赋予了超能力,能够自动适应各种变化。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private String name;private String mobile;protected Map<String,Object> extFields;@JsonAnySetterpublic void addFields(String key,Object value){if(MapUtil.isEmpty(extFields)){extFields = new HashMap<>();}extFields.put(key,value);}@JsonAnyGetterpublic Map<String,Object> getExtFields(){return extFields;}
}

测试代码:

 @PostMapping("json-any-setter")public User getUser(@RequestBody User user){UserUtil.print(user,"email","age");return user;}

单元测试:

  @Test@DisplayName("测试-通过@JsonAnySetter处理JSON对象中未被Java类中属性映射的字段")public void testJsonAnySetter() throws Exception{String json = """{"name": "lybgeek","mobile": "13800000000","email": "lybgeek@163.com","age": 18}"""MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user/json-any-setter").contentType(MediaType.APPLICATION_JSON_UTF8).content(json)).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();System.out.println(mvcResult.getResponse().getContentAsString());}

控制台输出:

name:lybgeek
mobile:13800000000
email:lybgeek@163.com
age:18
{"name":"lybgeek","mobile":"13800000000","email":"lybgeek@163.com","age":18}

优势: 在 Java 类中无需预先定义所有属性,能够动态地处理 JSON 中的额外字段,代码简洁且易于理解。
局限: 主要适用于处理简单的键值对形式的额外字段,如果 JSON 结构复杂,可能需要额外的处理逻辑。

方法四:自定义序列化与反序列化,深度定制

通过@JsonDeserialize和@JsonSerialize注解结合自定义序列化和反序列化类,实现更灵活的处理,就像是为你的数据处理量身定制一套专属的 “战甲”,无往而不利。

自定义反序列化类:

public class UserJsonDeserializer extends JsonDeserializer<User> {@Overridepublic User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {JsonNode jsonNode = p.getCodec().readTree(p);// 获取JSON对象中未被User类中属性映射的字段Map<String,Object> extFields = new HashMap<>();addFields(extFields,jsonNode,"email","age");User user = new User();user.setExtFields(extFields);user.setName(getValue(jsonNode,"name"));user.setMobile(getValue(jsonNode,"mobile"));return user;}public String getValue(JsonNode jsonNode, String key){if(jsonNode.has(key)){return jsonNode.get(key).asText();}return null;}public void addFields(Map<String,Object> extFields, JsonNode jsonNode, String...key){for(String k : key){if(jsonNode.has(k)){extFields.put(k,getValue(jsonNode,k));}}}

自定义序列化类:

public class UserJsonSerializer extends JsonSerializer<User> {@Overridepublic void serialize(User value, JsonGenerator gen, SerializerProvider serializers) throws IOException {Map<String,Object> userMap = new HashMap<>();userMap.put("name",value.getName());userMap.put("mobile",value.getMobile());if(MapUtil.isNotEmpty(value.getExtFields())){userMap.putAll(value.getExtFields());}gen.writeObject(userMap);}
}

在实体类上添加注解:

@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonDeserialize(using = UserJsonDeserializer.class)
@JsonSerialize(using = UserJsonSerializer.class)
public class User {private String name;private String mobile;protected Map<String,Object> extFields;
}

测试代码:

 @PostMapping("json-deserializer")public User getUser(@RequestBody User user){UserUtil.print(user,"email","age");return user;}

单元测试:

  @Test@DisplayName("测试-通过自定义反序列化+@JsonDeserialize处理JSON对象中未被Java类中属性映射的字段")public void testJsonDeserializer() throws Exception{String json = """{"name": "lybgeek","mobile": "13800000000","email": "lybgeek@163.com","age": 18}"""MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user/json-deserializer").contentType(MediaType.APPLICATION_JSON_UTF8).content(json)).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();System.out.println(mvcResult.getResponse().getContentAsString());}

控制台输出:

name:lybgeek
mobile:13800000000
email:lybgeek@163.com
age:18
{"name":"lybgeek","mobile":"13800000000","email":"lybgeek@163.com","age":18}

优势: 能够根据具体业务需求,对 JSON 数据的序列化和反序列化过程进行全面的控制,适用于复杂的业务场景。
局限: 开发成本较高,需要编写较多的代码,并且对开发者对 Jackson 库的理解要求较高。

方法五:@JsonComponent,全局掌控

使用@JsonComponent注解,将自定义的序列化器和反序列化器注册到 Jackson 的ObjectMapper中,实现全局统一处理,就像是给整个数据处理流程安排了一位 “大管家”,一切井井有条。

@JsonComponent
public class UserJsonComponent {public static class UserJsonDeserializer extends JsonDeserializer<User> {@Overridepublic User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {JsonNode jsonNode = p.getCodec().readTree(p);// 获取JSON对象中未被User类中属性映射的字段Map<String,Object> extFields = new HashMap<>();addFields(extFields,jsonNode,"email","age");User user = new User();user.setExtFields(extFields);user.setName(getValue(jsonNode,"name"));user.setMobile(getValue(jsonNode,"mobile"));return user;}public String getValue(JsonNode jsonNode, String key){if(jsonNode.has(key)){return jsonNode.get(key).asText();}return null;}public void addFields(Map<String,Object> extFields, JsonNode jsonNode, String...key){for(String k : key){if(jsonNode.has(k)){extFields.put(k,getValue(jsonNode,k));}}}public static class UserJsonSerializer extends JsonSerializer<User> {@Overridepublic void serialize(User value, JsonGenerator gen, SerializerProvider serializers) throws IOException {Map<String,Object> userMap = new HashMap<>();userMap.put("name",value.getName());userMap.put("mobile",value.getMobile());if(MapUtil.isNotEmpty(value.getExtFields())){userMap.putAll(value.getExtFields());}gen.writeObject(userMap);}
}

注: 通常情况下,序列化器和反序列化器类会定义为使用 @JsonComponent 注解的类的静态内部类。因为非静态内部类依赖于外部类的实例, 而在 Jackson 序列化和反序列化过程中,不会创建外部类的实例来调用非静态内部类的方法,所以必须使用静态内部类

测试代码:

 @PostMapping("json-component")public User getUser(@RequestBody User user){UserUtil.print(user,"email","age");return user;}

单元测试:

  @Test@DisplayName("通过自定义反序列化+@UserJsonComponent全局处理JSON对象中未被Java类中属性映射的字段-单元测试")public void testJsonComponent() throws Exception{String json = """{"name": "lybgeek","mobile": "13800000000","email": "lybgeek@163.com","age": 18}"""MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user/json-component").contentType(MediaType.APPLICATION_JSON_UTF8).content(json)).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();System.out.println(mvcResult.getResponse().getContentAsString());}

控制台输出:

name:lybgeek
mobile:13800000000
email:lybgeek@163.com
age:18
{"name":"lybgeek","mobile":"13800000000","email":"lybgeek@163.com","age":18}

优势: 实现了全局的统一配置,无需在每个实体类上分别添加注解,便于维护和管理。
局限: 如果项目中存在多个不同的 JSON 处理需求,可能会导致@JsonComponent注解的类变得过于庞大和复杂。

总结

本文详细介绍了在 Spring Boot 项目中处理 JSON 对象中未被 Java 类属性映射字段的多种方法,本质上都是围绕 JSON 与对象之间的序列化和反序列化展开。每种方法都有其独特的优势和适用场景,希望大家在实际开发中能够根据具体需求灵活选择。

福利:一键获取完整代码

为了方便大家学习和实践,我已经将完整的示例代码上传至 GitHub,点击下方链接即可获取:
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-dynamics-json
觉得文章有用的话,别忘了点赞、分享和订阅哦!你的支持是我创作的最大动力!如果在实践过程中有任何问题,欢迎在评论区留言,我们一起探讨。

http://www.aitangshan.cn/news/573.html

相关文章:

  • 【IEEE出版】第四届电力系统与电力工程国际学术会议(PSPE 2025)
  • 题解:P10299 [CCC 2024 S5] Chocolate Bar Partition
  • 关闭Ollama开机启动项
  • MySQL 根据一个表的字段值,更新另一个表的字段
  • DeepCompare文件深度对比软件:智能同步滚动与对比视图管理功能完全指南
  • 书单
  • 2025 款潘通色卡 PS/AI 插件推荐:解锁高效配色新体验
  • Dubbo源码—1.服务发布的主要流程
  • 剑指offer-20、包含min函数的栈
  • CF1456E XOR-ranges 题解
  • QueryCon 2019:osquery的重大转折点 - 技术治理与社区共建
  • 基于Transformer的百万级文本分类技术
  • 详细介绍:网络基础1-11综合实验(eNSP):vlan/DHCP/Web/HTTP/动态PAT/静态NAT
  • Omnissa Horizon Windows OS Optimization Tool 2506 - Windows 系统映像优化工具
  • docker 容器化部署 vLLM 启动大模型
  • App Linking 助力应用场景创新,操作步骤立省 60%
  • ChatGpt 5系列文章1——编码与智能体
  • Cisco Catalyst 9800-CL IOS XE 17.18.1 发布,新增功能简介
  • Cisco Modeling Labs (CML) 2.9.0 - 网络仿真工具
  • Omnissa App Volumes 4, version 2506 - 实时应用程序交付系统
  • Omnissa Dynamic Environment Manager 2506 - 个性化动态 Windows 桌面环境管理
  • AES 加密模式演进:从 ECB、CBC 到 GCM 的 C# 深度实践
  • Cisco Catalyst 9800 WLC IOS XE 17.18.1 发布,新增功能简介
  • 详细介绍:python办自动化--读取邮箱中特定的邮件,并下载特定的附件
  • 微软开源的 MCP 教程「GitHub 热点速览」
  • 题解:qoj10322 Matching Query
  • ZR Summer 2025 CD ACM暨 ZR Summer 2025 C 游记
  • flutter flutter_inappwebview插件里js上传调用相机和图库碰到的问题
  • ruoyi-cloud微服务docker部署
  • #dp#L 最多变的序列