如何仅为字段而非类添加Jackson序列化器?JPA继承实体序列化问题
这个需求在JPA+Jackson的组合场景里太常见了,我给你几个实用的方案,你可以根据自己的情况选择:
方案一:用@JsonValue注解(最简单直接)
如果你的需求是所有AbstractEntity及其子类在被序列化时,都只输出code属性,那用@JsonValue注解是最省心的方式。这个注解会告诉Jackson:当序列化这个类的实例时,直接输出被注解的属性值(或方法返回值),而不是整个对象。
只需要在你的AbstractEntity的code属性对应的getter方法(或者字段本身)上加上这个注解就行:
@MappedSuperclass public abstract class AbstractEntity { @Column(unique = true, nullable = false) private String code; // 其他属性、构造方法、setter... @JsonValue public String getCode() { return code; } }
这样一来,不管是ProductEntity里的categories集合(每个元素是CategoryEntity),还是CategoryEntity的parentCategory属性,序列化后都会直接输出code的字符串值,比如:
{ "code": "PROD001", "categories": ["CAT001", "CAT002"], // 其他Product属性... }
⚠️ 注意:这个方案会让所有AbstractEntity子类的实例在任何场景下序列化都只输出code,如果某个子类需要被完整序列化(比如单独返回一个UserEntity详情),这个方案就不适用了,得看下面的方案。
方案二:自定义序列化器(更灵活)
如果需要更灵活的控制(比如某些场景下序列化整个对象,某些场景下只输出code;或者想把code序列化成{"code": "XXX"}的对象形式),可以自定义一个Jackson序列化器,专门处理AbstractEntity类型。
步骤1:编写自定义序列化器
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import java.io.IOException; public class AbstractEntityCodeSerializer extends StdSerializer<AbstractEntity> { // 必须的无参构造方法 protected AbstractEntityCodeSerializer() { super(AbstractEntity.class); } @Override public void serialize(AbstractEntity entity, JsonGenerator gen, SerializerProvider provider) throws IOException { // 如果你想输出纯字符串形式的code,用这个: gen.writeString(entity.getCode()); // 如果你想输出对象形式(比如{"code": "CAT001"}),替换成下面的代码: /* gen.writeStartObject(); gen.writeStringField("code", entity.getCode()); gen.writeEndObject(); */ } }
步骤2:绑定序列化器到AbstractEntity
有两种绑定方式:
方式A:直接在AbstractEntity上加注解
@MappedSuperclass @JsonSerialize(using = AbstractEntityCodeSerializer.class) public abstract class AbstractEntity { // 原有属性和方法... }
方式B:全局注册(适合不能修改AbstractEntity源码的场景)
如果你的AbstractEntity是第三方库的类,或者不想修改它的代码,可以在Jackson的配置类里全局注册这个序列化器:
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.addSerializer(AbstractEntity.class, new AbstractEntityCodeSerializer()); mapper.registerModule(module); return mapper; } }
这个方案的优势是可以灵活调整序列化逻辑,比如后续如果需要给某些子类添加额外的序列化字段,只需要修改序列化器的逻辑(比如判断实体类型,针对性处理)。
方案三:用@JsonIdentityInfo(兼顾循环引用和简洁输出)
如果你的实体存在循环引用(比如CategoryEntity的parentCategory和children互相引用),除了只输出code,还想避免Jackson的循环引用报错,可以结合@JsonIdentityInfo和@JsonIdentityReference:
@MappedSuperclass @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "code") public abstract class AbstractEntity { private String code; // getter、setter... }
然后在关联属性上加上@JsonIdentityReference(alwaysAsId = true):
@Entity public class CategoryEntity extends AbstractEntity { @ManyToOne @JsonIdentityReference(alwaysAsId = true) private CategoryEntity parentCategory; @OneToMany(mappedBy = "parentCategory") @JsonIdentityReference(alwaysAsId = true) private List<CategoryEntity> children; // 其他属性... }
这样序列化时,关联的实体都会直接输出code,同时也解决了循环引用的问题。
内容的提问来源于stack exchange,提问作者Petar Tahchiev

