如何用Jackson根据category字段反序列化多类型Value的JSON?
基于Jackson根据父对象字段动态反序列化子对象类型
场景说明
有如下JSON结构,result中的value类型由同层级的category字段决定:
{ "result": { "id": "123", "category": "typeA", "value": { "typeAField": "123" } } }
{ "result": { "id": "321", "category": "typeB", "value": { "typeBField1": "123", "typeBField2": true } } }
对应的Java类结构已定义:
public class Result { String id; String category; Values value; } public abstract class Values { } public class TypeA extends Values { String typeAField; } public class TypeB extends Values { String typeBField1; Boolean typeBField2; }
遇到的问题
尝试使用@JsonTypeInfo注解时抛出错误:
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'typeAField' as a subtype of `Values`: known type ids = [typeA, typeB] (for POJO property 'value') at [Source: (String)"{"id":"123","category":"typeA","value":{"typeAField":"123"}}"; line: 1, column: 41] (through reference chain: Result["value"])
目前通过自定义@JsonSetter实现了功能,但代码冗余,需要更简洁的方案。
最优解决方案:结合@JsonTypeInfo与@JsonTypeIdResolver
因为类型标识(category)不在value对象内部,而是在其父对象Result中,所以需要自定义类型解析器从父对象获取类型id:
步骤1:自定义TypeIdResolver
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; import com.fasterxml.jackson.databind.DatabindContext; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase; public class ValuesTypeIdResolver extends TypeIdResolverBase { @Override public Id getMechanism() { return Id.NAME; } @Override public String idFromValue(Object value) { if (value instanceof TypeA) { return "typeA"; } else if (value instanceof TypeB) { return "typeB"; } return null; } @Override public String idFromValueAndType(Object value, Class<?> suggestedType) { return idFromValue(value); } @Override public JavaType typeFromId(DatabindContext context, String id) { switch (id) { case "typeA": return context.constructType(TypeA.class); case "typeB": return context.constructType(TypeB.class); default: throw new IllegalArgumentException("Unknown category: " + id); } } }
步骤2:配置Result类的value字段
在Result类的value字段上添加注解,指定类型解析规则:
import com.fasterxml.jackson.annotation.JsonTypeInfo; public class Result { String id; String category; @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "category", visible = true, resolver = ValuesTypeIdResolver.class ) Values value; // 添加getter/setter保证Jackson可访问字段 public String getId() { return id; } public void setId(String id) { this.id = id; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } public Values getValue() { return value; } public void setValue(Values value) { this.value = value; } }
步骤3:验证反序列化
import com.fasterxml.jackson.databind.ObjectMapper; public class Test { public static void main(String[] args) throws Exception { String json1 = "{\"id\":\"123\",\"category\":\"typeA\",\"value\":{\"typeAField\":\"123\"}}"; ObjectMapper mapper = new ObjectMapper(); Result result1 = mapper.readValue(json1, Result.class); System.out.println(result1.getValue().getClass()); // 输出class TypeA String json2 = "{\"id\":\"321\",\"category\":\"typeB\",\"value\":{\"typeBField1\":\"123\",\"typeBField2\":true}}"; Result result2 = mapper.readValue(json2, Result.class); System.out.println(result2.getValue().getClass()); // 输出class TypeB } }
方案优势
- 完全基于Jackson注解实现,代码简洁,无需手动处理JSON节点
- 同时支持序列化和反序列化
- 类型解析逻辑集中在Resolver类,易于维护和扩展
内容的提问来源于stack exchange,提问作者carrot
相关产品推荐
相关产品推荐

