Spring Boot一个接口的JSON参数怎么自动适配不同的子类

443 阅读1分钟

在java中,多态是非常常见的,一个父类与多个子类,json也是常用的数据格式,我们偶尔会遇到需要将json数据根据不同的场景转化为不同的实体的情况,这时可以使用json的@JsonTypeInfo与@JsonSubTypes注解。
SpringBoot后端接口接收json类型的参数时,默认是使用jackson来将json数据转化为对应的实例对象,因为了解了jackson怎么将json映射到不同的类就可以了,在spring boot中的用法也是一样的。

jackson示例代码

java版本:21
jackson版本

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

基类

在基类上使用@JsonTypeInfo注解
use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY表示使用已存在的某个属性来判断序列化成哪个类
property = "type"表示用type属性来判断序列化成哪个类 defaultImpl = PublicParent.class表示默认的实现是哪个类,当type没有匹配到具体的类时,对应的json都会转为PublicParent

@JsonSubTypes可以注册多个子类,每个子类用@JsonSubTypes.Type声明
@JsonSubTypes.Type(value = SonA.class, name = "a")声明了一个子类SonA,其中name = "a"表示当type字段的值为a时,将json转为SonA,name字段与@JsonTypeInfo中的property的值配合

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

import lombok.Getter;
import lombok.Setter;

/**
 * 公共父类
 *
 * @since 2024/3/27
 **/
@Setter
@Getter
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type",
    defaultImpl = PublicParent.class)
@JsonSubTypes({
    @JsonSubTypes.Type(value = SonA.class, name = "a")
})
public class PublicParent {
    private String type;

    private String name;
}

子类

子类不需要任何额外修改

import lombok.Getter;
import lombok.Setter;

/**
 * 孩子A
 *
 * @since 2024/3/27
 **/
@Setter
@Getter
public class SonA extends PublicParent {
    private String a;
}

单元测试

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

class PublicParentTest {
    private static final ObjectMapper mapper = new ObjectMapper();

    @BeforeAll
    static void beforeAll() {
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    @Test
    void should_return_parent_when_not_son() {
        String json = """
            {"type": "b", "name": "parent", "a": "i is a"}""";
        PublicParent res = Assertions.assertDoesNotThrow(() -> mapper.readValue(json, PublicParent.class));
        Assertions.assertEquals("parent", res.getName());
    }

    @Test
    void should_return_son_when_is_son() {
        String json = """
            {"type": "a", "name": "son", "a": "i is a"}""";
        SonA res = (SonA) Assertions.assertDoesNotThrow(() -> mapper.readValue(json, PublicParent.class));
        Assertions.assertEquals("i is a", res.getA());
    }
}