You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 22:40:44