如何用Jackson实现多子类型接口序列化以匹配指定JSON结构
问题描述
目标JSON格式(同一时间仅包含actionA/actionB/actionC中的一个):
{ "name": "foo", "actionA": { .... } }
当前使用Jackson序列化后得到的JSON多了一层action嵌套,不符合需求:
{ "name": "foo", "action": { "actionA": { ... } } }
现有Java类结构:
public record Content(String name, Action action) { ... }
@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT) @JsonSubTypes({ @JsonSubTypes.Type(value = ActionA.class), @JsonSubTypes.Type(value = ActionB.class), @JsonSubTypes.Type(value = ActionC.class) }) public interface Action { ... }
@JsonTypeName("actionA") public record ActionA(...) { ... }
@JsonTypeName("actionB") public record ActionB(...) { ... }
@JsonTypeName("actionC") public record ActionC(...) { ... }
解决方案
可以通过@JsonAnyGetter和@JsonAnySetter实现将action的内容直接展开到Content的顶层属性中,具体修改如下:
- 修改
Content记录,添加动态属性的获取和设置方法:
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; public record Content(String name, Action action) { // 复用ObjectMapper避免重复创建 private static final ObjectMapper MAPPER = new ObjectMapper(); @JsonAnyGetter public Map<String, Object> getActionProperties() { // 将Action实例转换为包含类型名作为键的Map return MAPPER.convertValue(action, new TypeReference<Map<String, Object>>() {}); } @JsonAnySetter public void setActionProperty(String key, Object value) throws Exception { // 根据键名匹配对应的Action子类 switch (key) { case "actionA" -> action = MAPPER.convertValue(value, ActionA.class); case "actionB" -> action = MAPPER.convertValue(value, ActionB.class); case "actionC" -> action = MAPPER.convertValue(value, ActionC.class); default -> throw new IllegalArgumentException("Unknown action type: " + key); } } }
- 保留原有
Action接口和子类的注解不变,MAPPER.convertValue会利用@JsonTypeName和@JsonTypeInfo将Action实例序列化为以类型名为键的对象,再通过@JsonAnyGetter将这个键值对展开到Content的顶层。
修改完成后,序列化Content实例时,actionA/actionB/actionC会直接和name同级,完全匹配目标JSON格式。
内容的提问来源于stack exchange,提问作者Erik
相关产品推荐
相关产品推荐

