如何用Jackson将含未知属性名的JSON反序列化为泛型类型
Jackson泛型类映射动态属性到指定字段问题解决
问题场景
需要反序列化包含固定id字段和一个动态属性名(如human/cat/bike)的JSON,动态属性的类型编译时已知,目标是将该动态属性映射到泛型类Data<T>的other字段。现有代码因Jackson无法识别动态属性名,抛出错误:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "human"
解决方案
方案1:使用@JsonAnySetter捕获动态属性
针对@Value生成的不可变类,添加@JsonAnySetter注解的方法接收所有未知属性,并赋值给other字段:
@Value @Builder @Jacksonized public class Data<T> { String id; T other; // 接收所有未知属性,将值赋值给other @JsonAnySetter private void setOther(String key, T value) { this.other = value; } }
使用时必须通过TypeReference保留泛型信息,否则Jackson无法推断T的具体类型:
Data<Human> humanData = objectMapper.readValue(jsonHumanString, new TypeReference<Data<Human>>() {}); Data<Cat> catData = objectMapper.readValue(jsonCatString, new TypeReference<Data<Cat>>() {});
方案2:自定义泛型反序列化器
实现自定义反序列化器,手动解析JSON并提取动态属性:
public class DataDeserializer<T> extends JsonDeserializer<Data<T>> { private final Class<T> targetType; public DataDeserializer(Class<T> targetType) { this.targetType = targetType; } @Override public Data<T> deserialize(JsonParser parser, DeserializationContext context) throws IOException { JsonNode rootNode = parser.getCodec().readTree(parser); String id = rootNode.get("id").asText(); // 遍历除id外的字段,取第一个动态属性 T other = null; for (Iterator<String> fieldNames = rootNode.fieldNames(); fieldNames.hasNext(); ) { String fieldName = fieldNames.next(); if (!"id".equals(fieldName)) { JsonNode otherNode = rootNode.get(fieldName); other = parser.getCodec().treeToValue(otherNode, targetType); break; } } return Data.<T>builder().id(id).other(other).build(); } }
注册反序列化器并使用:
// 注册针对Human类型的反序列化器 ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.addDeserializer(Data.class, new DataDeserializer<>(Human.class)); mapper.registerModule(module); Data<Human> humanData = mapper.readValue(jsonHumanString, Data.class);
方案3:结合@JsonIgnoreProperties与泛型类型推断
先允许Jackson忽略未知属性,再通过反射获取泛型实际类型,将动态属性转换为T类型:
@Value @Builder @Jacksonized @JsonIgnoreProperties(ignoreUnknown = true) public class Data<T> { String id; T other; @JsonAnySetter private void parseOther(String key, JsonNode value) throws JsonProcessingException { // 通过反射获取泛型实际类型 Class<T> type = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; this.other = new ObjectMapper().treeToValue(value, type); } }
使用时同样需要用TypeReference保留泛型信息:
Data<Bike> bikeData = objectMapper.readValue(jsonBikeString, new TypeReference<Data<Bike>>() {});
内容的提问来源于stack exchange,提问作者Sean Nesbit
相关产品推荐
相关产品推荐

