自定义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注解处理器:
- Spring Boot默认的ObjectMapper会配置一套完整的注解处理器,包括处理
@JsonProperty字段映射、反序列化规则等逻辑。 - 你通过
objectMapper.setAnnotationIntrospector(new MaskAnnotationIntrospector())直接替换了默认处理器,虽然自定义处理器继承了JacksonAnnotationIntrospector,但Spring Boot默认添加的额外配置(比如驼峰转下划线、@JsonProperty的字段映射)都丢失了,导致反序列化时无法识别注解指定的字段名。 - 自定义的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
相关产品推荐
相关产品推荐

