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

自定义ObjectMapper引发Jackson反序列化异常问题排查

自定义Jackson序列化器引发反序列化@JsonProperty字段异常问题

环境

  • Spring Boot v2.4.3
  • Jackson v2.12.2

问题背景

为实现DTO敏感字段掩码功能,我自定义了ObjectMapper和注解处理器,原本只希望逻辑作用于序列化环节,但实际却影响了反序列化流程——所有带@JsonProperty注解的字段都会被识别为全小写(比如@JsonProperty("fieldName")的字段会被当成fieldnamelikethis),调用未使用掩码注解的API时抛出如下异常:

[HttpMessageNotReadableException: JSON parse error: Unrecognized field "fieldName"]

相关代码

WebMvc配置类

@Configuration
public class MyConfiguration<T> implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        mappingJackson2HttpMessageConverter.setDefaultCharset(StandardCharsets.UTF_8);
        converters.add(mappingJackson2HttpMessageConverter);
        converters.add(marshallingMessageConverter());
        converters.add(createXstreamXmlHttpMessageConverter());
        converters.add(new ByteArrayHttpMessageConverter());
    }

}

自定义ObjectMapper配置

@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setAnnotationIntrospector(new MaskAnnotationIntrospector());
        return objectMapper;
    }
}

掩码注解及序列化器实现

注解处理器

public class MaskAnnotationIntrospector extends JacksonAnnotationIntrospector {

    @Override
    public Object findSerializer(Annotated a) {
        MaskJson maskJsonAnnotation = a.getAnnotation(MaskJson.class);
        if (maskJsonAnnotation != null) {
            return MaskingSerializer.class;
        }
        return super.findSerializer(a);
    }
}

掩码注解

@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JsonSerialize(using = MaskingSerializer.class)
public @interface MaskJson {
    MaskingTypeEnum type();
}

序列化器

public class MaskingSerializer extends StdSerializer<String> implements ContextualSerializer {

    private MaskingTypeEnum maskingType;

    protected MaskingSerializer(MaskingTypeEnum maskingType) {
        super(String.class);
        this.maskingType = maskingType;
    }

    public MaskingSerializer() {
        super(String.class);
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
        if (property != null) {
            MaskJson maskJson = property.getAnnotation(MaskJson.class);
            if (maskJson != null) {
                MaskingTypeEnum maskingTypeValue = maskJson.type();
                return new MaskingSerializer(maskingTypeValue);
            }
        }
        return this;
    }

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeString(DisplayUtils.getMaskingData(value, maskingType));
    }

}

异常场景代码

相关API

@PostMapping(value = "/test/all", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseDTO getAll(@RequestBody SearchRequest request) throws ParseException {

}

请求DTO

@Getter
@ToString(callSuper = true)
public class SearchRequest extends CommonSearch implements SearchParam {
}

@Getter
@ToString
@MemberIdentifierValid
public class CommonSearch {
    private String memNo;
    private String info;

    @JsonProperty("fieldName") private String fieldname;
}

问题原因

核心问题出在替换了全局的Jackson注解处理器:

  1. Spring Boot默认的ObjectMapper会配置一套完整的注解处理器,包括处理@JsonProperty字段映射、反序列化规则等逻辑。
  2. 你通过objectMapper.setAnnotationIntrospector(new MaskAnnotationIntrospector())直接替换了默认处理器,虽然自定义处理器继承了JacksonAnnotationIntrospector,但Spring Boot默认添加的额外配置(比如驼峰转下划线、@JsonProperty的字段映射)都丢失了,导致反序列化时无法识别注解指定的字段名。
  3. 自定义的ObjectMapper会被Spring Boot全局注入到所有MappingJackson2HttpMessageConverter实例中,所以反序列化环节也会使用这个被修改过的ObjectMapper。

解决方案

方案1:组合自定义与默认注解处理器(推荐)

不要直接替换,而是将自定义处理器与默认处理器组合,保留原有逻辑:

@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper(ObjectMapper defaultObjectMapper) {
        // 获取Spring Boot默认的注解处理器
        AnnotationIntrospector defaultIntrospector = defaultObjectMapper.getAnnotationIntrospector();
        // 组合自定义处理器和默认处理器,自定义逻辑优先处理序列化
        AnnotationIntrospector combinedIntrospector = AnnotationIntrospectorPair.pair(new MaskAnnotationIntrospector(), defaultIntrospector);
        defaultObjectMapper.setAnnotationIntrospector(combinedIntrospector);
        return defaultObjectMapper;
    }
}

方案2:通过Module注册自定义逻辑

使用Jackson的Module机制注册序列化器,避免修改全局注解处理器:

public class MaskModule extends SimpleModule {
    @Override
    public void setupModule(SetupContext context) {
        super.setupModule(context);
        // 将自定义处理器插入到处理器链前端,仅影响序列化
        context.insertAnnotationIntrospector(new MaskAnnotationIntrospector());
    }
}

然后在ObjectMapper中注册该Module:

@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper(ObjectMapper defaultObjectMapper) {
        defaultObjectMapper.registerModule(new MaskModule());
        return defaultObjectMapper;
    }
}

方案3:修正WebMvc配置(可选)

手动添加MappingJackson2HttpMessageConverter时,注入容器中的ObjectMapper,避免创建新实例:

@Configuration
public class MyConfiguration implements WebMvcConfigurer {
    private final ObjectMapper objectMapper;

    public MyConfiguration(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(objectMapper);
        jacksonConverter.setDefaultCharset(StandardCharsets.UTF_8);
        converters.add(jacksonConverter);
        // 其他转换器...
    }
}

内容的提问来源于stack exchange,提问作者Jason

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 17:56:05