Spring Boot中Jackson如何选择首个非空别名字段映射POJO?
解决Jackson映射时优先选择首个非空别名字段的问题
默认情况下,Jackson的@JsonAlias仅实现多JSON字段到单个POJO属性的映射,但如果多个字段同时存在(部分为空),它会按解析顺序覆盖值,不会自动筛选非空字段。要实现优先取首个非空候选字段的逻辑,需要自定义Jackson的反序列化逻辑,以下是具体实现方案:
全局解决方案(推荐)
通过自定义BeanDeserializerModifier和字段反序列化器,让所有带@JsonAlias的字段自动应用"取首个非空值"的规则。
1. 自定义字段反序列化器
该反序列化器会遍历所有候选字段名,找到第一个非空的JSON节点并解析:
public class FirstNonNullAliasDeserializer extends StdScalarDeserializer<Object> implements ContextualDeserializer { private final BeanProperty property; private final List<String> candidateNames; private final JsonDeserializer<?> delegate; private FirstNonNullAliasDeserializer(BeanProperty property, List<String> candidateNames, JsonDeserializer<?> delegate) { super(delegate.getValueType()); this.property = property; this.candidateNames = candidateNames; this.delegate = delegate; } @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode node = p.readValueAsTree(); // 按顺序遍历候选字段,取第一个非空值 for (String name : candidateNames) { JsonNode fieldNode = node.get(name); if (fieldNode != null && !fieldNode.isNull()) { return delegate.deserialize(p.getCodec().treeAsTokens(fieldNode), ctxt); } } // 所有字段为空时返回默认值 return delegate.getNullValue(ctxt); } @Override public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { return this; } public static FirstNonNullAliasDeserializer of(BeanProperty property, List<String> candidateNames, JsonDeserializer<?> delegate) { return new FirstNonNullAliasDeserializer(property, candidateNames, delegate); } }
2. 自定义BeanDeserializerModifier
该Modifier会拦截Bean的反序列化过程,为带@JsonAlias的字段替换成上面的自定义反序列化器:
public class FirstNonNullAliasModifier extends BeanDeserializerModifier { @Override public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) { if (deserializer instanceof BeanDeserializer) { BeanDeserializer beanDeserializer = (BeanDeserializer) deserializer; Map<String, SettableBeanProperty> properties = new HashMap<>(beanDeserializer.getProperties()); for (Map.Entry<String, SettableBeanProperty> entry : properties.entrySet()) { SettableBeanProperty property = entry.getValue(); JsonAlias aliasAnn = property.getAnnotation(JsonAlias.class); if (aliasAnn != null) { // 收集所有候选字段:@JsonProperty指定的字段名 + @JsonAlias的别名 List<String> candidateNames = new ArrayList<>(); candidateNames.add(property.getName()); candidateNames.addAll(Arrays.asList(aliasAnn.value())); // 包装原有反序列化器 JsonDeserializer<?> originalDeser = property.getValueDeserializer(); FirstNonNullAliasDeserializer customDeser = FirstNonNullAliasDeserializer.of(property, candidateNames, originalDeser); // 更新字段的反序列化器 SettableBeanProperty newProperty = property.withValueDeserializer(customDeser); properties.put(entry.getKey(), newProperty); } } // 返回修改后的BeanDeserializer return beanDeserializer.withProperties(properties); } return super.modifyDeserializer(config, beanDesc, deserializer); } }
3. 注册到Spring Boot的ObjectMapper
在配置类中把自定义的Modifier注册到Jackson的模块:
@Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.setDeserializerModifier(new FirstNonNullAliasModifier()); objectMapper.registerModule(module); return objectMapper; } }
4. POJO的写法
保持原有@JsonProperty和@JsonAlias的写法即可,候选字段的优先级是@JsonProperty的字段名在前,然后是@JsonAlias中定义的别名顺序:
public class User { @JsonProperty("name") @JsonAlias({"full_name", "fullName"}) private String username; // getter、setter省略 }
单个字段解决方案
如果只需要针对个别字段处理,可以直接在字段上指定自定义反序列化器,适合局部场景:
public class User { @JsonProperty("name") @JsonAlias({"full_name", "fullName"}) @JsonDeserialize(using = UsernameDeserializer.class) private String username; // 自定义单个字段的反序列化器 public static class UsernameDeserializer extends StdDeserializer<String> { protected UsernameDeserializer() { super(String.class); } @Override public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode node = p.readValueAsTree(); // 按顺序检查字段 if (node.has("name") && !node.get("name").isNull()) { return node.get("name").asText(); } else if (node.has("full_name") && !node.get("full_name").isNull()) { return node.get("full_name").asText(); } else if (node.has("fullName") && !node.get("fullName").isNull()) { return node.get("fullName").asText(); } return null; } } }
内容的提问来源于stack exchange,提问作者Rafael Lima
相关产品推荐
相关产品推荐

