Spring Boot 一招解决字段适配json噩梦!

341 阅读3分钟

点击上方“程序员蜗牛g”,选择“设为星标”

跟蜗牛哥一起,每天进步一点点

程序员蜗牛g

大厂程序员一枚 跟蜗牛一起 每天进步一点点

33篇原创内容

**

公众号

对于 Java 开发者来说,JSON 字段的不确定性就像一个无形的陷阱—— 前端改个字段名、加个动态属性,后端解析立刻崩溃。

设想这样一个场景:

{  "name""icoderoad",  "mobile""13900000000",  "extFields": {    "email""icoderoad@gmail.com",    "age"2  }}

或者换一种写法:

{  "name""icoderoad",  "mobile""13900000000",  "email""icoderoad@gmail.com",  "age"22}

而你的后端实体类 /src/main/java/com/icoderoad/model/User.java 却是这样的:

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

这种不匹配让无数后端开发者陷入了“字段地狱”。 本文将深入解析三种经典解决方案,让 Spring Boot 从容应对任意结构的 JSON 数据!

方案一:Map接收法 —— 轻量又实用

当仅需临时存储额外字段时,用 Map<String, Object> 是最直接的方式。

示例代码

路径:/src/main/java/com/icoderoad/model/User.java

package com.icoderoad.model;
import lombok.*;import java.util.Map;
@Data@AllArgsConstructor@NoArgsConstructorpublic class User {    private String name;    private String mobile;    protected Map<StringObject> extFields;}

路径:/src/main/java/com/icoderoad/controller/UserController.java

package com.icoderoad.controller;
import com.icoderoad.model.User;import com.icoderoad.util.UserUtil;import org.springframework.web.bind.annotation.*;
@RestController@RequestMapping("/user")public class UserController {
    @PostMapping("/json-map")    public User getUser(@RequestBody User user) {        UserUtil.print(user, "email""age");        return user;    }}

路径:/src/main/java/com/icoderoad/util/UserUtil.java

package com.icoderoad.util;
import com.icoderoad.model.User;import org.apache.commons.lang3.ArrayUtils;
public final class UserUtil {    private UserUtil() {}
    public static void print(User user, String... keys) {        System.out.println("name: " + user.getName());        System.out.println("mobile: " + user.getMobile());        if (ArrayUtils.isNotEmpty(keys)) {            for (String k : keys) {                System.out.println(k + ": " + user.getExtFields().get(k));            }        }    }}

控制台输出:

name: icoderoadmobile: 13900000000email: icoderoad@gmail.comage: 22

优点:实现简单,适合轻量场景。缺点:可读性不强,维护复杂结构时代码臃肿。

方案二:JsonNode接收法 —— 结构复杂的利器

当 JSON 层次较深或结构不固定时,JsonNode 是更专业的选择。 它来自 Jackson 库,可以精确访问任意节点。

Maven 依赖

<dependency>    <groupId>com.fasterxml.jackson.core</groupId>    <artifactId>jackson-databind</artifactId>    <version>2.13.0</version></dependency>

实体类

路径:/src/main/java/com/icoderoad/model/User.java

package com.icoderoad.model;
import com.fasterxml.jackson.databind.JsonNode;import lombok.*;
@Data@AllArgsConstructor@NoArgsConstructorpublic class User {    private String name;    private String mobile;    private JsonNode extFields;}

控制器

路径:/src/main/java/com/icoderoad/controller/UserController.java

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

测试输出

name: icoderoadmobile: 13900000000email: icoderoad@gmail.comage: 22

优势

  • 可解析任意深度的嵌套结构;
  • 支持动态访问节点属性;
  • 与 Jackson 紧密集成。

劣势

  • 操作较复杂;
  • 学习曲线略高。

方案三:@JsonAnySetter / @JsonAnyGetter —— Jackson 双剑合璧

这对注解是 JSON 适配的“终极解决方案”。

 它能在反序列化和序列化过程中,动态接收与输出未知字段

示例代码

路径:/src/main/java/com/icoderoad/model/User.java

package com.icoderoad.model;
import com.fasterxml.jackson.annotation.*;import lombok.*;import java.util.*;
@Data@AllArgsConstructor@NoArgsConstructorpublic class User {    private String name;    private String mobile;
    private Map<StringObject> extFields = new HashMap<>();
    @JsonAnySetter    public void setUnknownField(String key, Object value) {        extFields.put(key, value);    }
    @JsonAnyGetter    public Map<StringObjectgetUnknownFields() {        return extFields;    }}

控制器

路径:/src/main/java/com/icoderoad/controller/UserController.java

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

输出结果:

name: icoderoadmobile: 13900000000email: icoderoad@gmail.comage: 22

优势

  • 动态接收和输出未知字段;
  • 代码更清晰、语义更强;
  • 特别适合字段频繁变化的系统。

案例实操:在线商城中的动态促销信息

假设我们的商品 JSON 如下:

{  "productName": "智能手表",  "price": 1999.00,  "stock": 100,  "promotionInfo": {    "discount": 0.8,    "fullReduce": {      "condition": 2000,      "reduction": 500    }  }}

后端实体类 /src/main/java/com/icoderoad/model/Product.java

@Data@AllArgsConstructor@NoArgsConstructorpublic class Product {    private String productName;    private Double price;    private Integer stock;    private Map<StringObject> promotionInfo;}

控制器 /src/main/java/com/icoderoad/controller/ProductController.java

@RestController@RequestMapping("/product")public class ProductController {
    @PostMapping("/map")    public Product saveProduct(@RequestBody Product product) {        System.out.println("商品名称:" + product.getProductName());        System.out.println("价格:" + product.getPrice());        System.out.println("库存:" + product.getStock());        System.out.println("促销信息:" + product.getPromotionInfo());        return product;    }}

若促销信息结构更复杂,可改用 JsonNode

private JsonNode promotionInfo;

即可轻松访问嵌套节点:

Double discount = product.getPromotionInfo().get("discount").asDouble();

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!