申明:本人一般不写博客,苦于此问题资料太零散。
故事从 jackson 的 @JsonFormat 和 spring format 下的
@DateTimeFormat 说起
@JsonFormat :响应数据生效 (实测接收body参数也生效)
@DateTimeFormat:接收参数生效(测试GET参数的确生效)
问题引入:
全局配置时间格式,无论输出还是输入,全局高精度Number转字符串
码代码:
json全局序列化和反序列化配置:
自定义实现Jackson2ObjectMapperBuilderCustomizer或者注入自定义的ObjectMapper
然而,响应生效,接收 body参数也生效,get参数不生效. emm...,难道是哪里写漏了?比如说WebMvcConfigurer.MappingJackson2HttpMessageConverter.setObjectMapper ?需要重写HttpMessageConverter么?
继续捯饬:一通操作猛如虎,结果毫无作用。
好吧,根据报错,进入断点模式:持续跟踪,发现 从WebDataBinder 慢慢直接就走入了org.springframework.core.convert.support.ConversionUtils并没有进入jackson包;抛去各种非全局的解决办法,最后自定义Converter解决问题。
代码如下:
JacksonConfig.java
package com.mrcode.case.config; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import java.io.IOException; import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.BigInteger; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; /** * @author mrcode@126.com * @date 2022/4/2 16:24 */ @Configuration @ConditionalOnClass(ObjectMapper.class) @AutoConfigureBefore(JacksonAutoConfiguration.class) @Slf4j public class JacksonConfig { final String FORMATTER_DATE_TIME_H = "yyyy-MM-dd HH:mm:ss"; final String FORMATTER_DATE_TIME_S = "yyyy/MM/dd HH:mm:ss"; final String FORMATTER_DATE_H = "yyyy-MM-dd"; final String FORMATTER_DATE_S = "yyyy/MM/dd"; final String FORMATTER_TIME = "HH:mm:ss"; final String H = "-"; final String S = "/"; final String P = "."; final String ZERO_INT64 = "0"; @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public Jackson2ObjectMapperBuilderCustomizer customJacksonBuilder() { return builder -> { /** * 配置Date的格式 */ builder.locale(Locale.CHINA); builder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault())); builder.simpleDateFormat(FORMATTER_DATE_TIME_H); builder.deserializerByType(Date.class, new DateDeserializer()); /** * 配置java8时间格式 */ builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(FORMATTER_DATE_TIME_H))); builder.serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(FORMATTER_DATE_H))); builder.serializerByType(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(FORMATTER_TIME))); builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer()); builder.deserializerByType(LocalDate.class, new LocalDateDeserializer()); builder.deserializerByType(LocalTime.class, new LocalTimeDeserializer()); /** * 配置高精度数字字符串格式 */ builder.serializerByType(Long.TYPE, ToStringSerializer.instance); builder.serializerByType(Long.class, ToStringSerializer.instance); builder.serializerByType(BigInteger.class, ToStringSerializer.instance); builder.serializerByType(BigDecimal.class, ToStringSerializer.instance); }; } @Bean @Primary public ObjectMapper customObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build(); objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() { @Override public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { String fieldName = jsonGenerator.getOutputContext().getCurrentName(); try { Field field = jsonGenerator.getCurrentValue().getClass().getDeclaredField(fieldName); if (Long.class.isAssignableFrom(field.getType())) { jsonGenerator.writeString(ZERO_INT64); } else if (BigInteger.class.isAssignableFrom(field.getType())) { jsonGenerator.writeString(ZERO_INT64); } else if (BigDecimal.class.isAssignableFrom(field.getType())) { jsonGenerator.writeString(ZERO_INT64); } else if (CharSequence.class.isAssignableFrom(field.getType())) { jsonGenerator.writeString(ZERO_INT64); } else if (Collection.class.isAssignableFrom(field.getType())) { jsonGenerator.writeStartArray(); jsonGenerator.writeEndArray(); } else if (Map.class.isAssignableFrom(field.getType())) { jsonGenerator.writeStartObject(); jsonGenerator.writeEndObject(); } } catch (NoSuchFieldException ignored) { log.error("", ignored); } } }); /** * 不序列化Null值字段 */ builder.serializationInclusion(JsonInclude.Include.NON_NULL); /** * 忽略序列化空对象错误 */ objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); /** * 忽略反序列化未知属性 */ objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); return objectMapper; } public class DateDeserializer extends JsonDeserializer<Date> { @Override public Date deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException { String value = p.getValueAsString(); SimpleDateFormat formatter = new SimpleDateFormat(); if (value.contains(H)) { formatter = new SimpleDateFormat(FORMATTER_DATE_TIME_H); } else if (value.contains(S)) { formatter = new SimpleDateFormat(FORMATTER_DATE_TIME_S); } try { return formatter.parse(value); } catch (ParseException e) { return deserializationContext.parseDate(value); } } } public class LocalTimeDeserializer extends JsonDeserializer<LocalTime> { @Override public LocalTime deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException { String value = p.getValueAsString(); if (value.contains(P)) { return LocalTime.parse(value); } else { return LocalTime.parse(value, DateTimeFormatter.ofPattern(FORMATTER_TIME)); } } } public class LocalDateDeserializer extends JsonDeserializer<LocalDate> { @Override public LocalDate deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException { String value = p.getValueAsString(); if (value.contains(H)) { return LocalDate.parse(value, DateTimeFormatter.ofPattern(FORMATTER_DATE_H)); } else if (value.contains(S)) { return LocalDate.parse(value, DateTimeFormatter.ofPattern(FORMATTER_DATE_S)); } else { return LocalDate.parse(value); } } } public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> { @Override public LocalDateTime deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException { String value = p.getValueAsString(); if (value.contains(H)) { return LocalDateTime.parse(value, DateTimeFormatter.ofPattern(FORMATTER_DATE_TIME_H)); } else if (value.contains(S)) { return LocalDateTime.parse(value, DateTimeFormatter.ofPattern(FORMATTER_DATE_TIME_S)); } else { return LocalDateTime.parse(value); } } } }
WebMvcConfig.java
package com.mrcode.case.config; import com.fasterxml.jackson.databind.util.ClassUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.Date; /** * @author mrcode@126.com * @date 2022/4/2 16:24 */ @Slf4j @AutoConfigureAfter(JacksonConfig.class) @Configuration public class WebMvcConfig implements WebMvcConfigurer { final String FORMATTER_DATE_TIME_H = "yyyy-MM-dd HH:mm:ss"; final String FORMATTER_DATE_TIME_S = "yyyy/MM/dd HH:mm:ss"; final String FORMATTER_DATE_H = "yyyy-MM-dd"; final String FORMATTER_DATE_S = "yyyy/MM/dd"; final String FORMATTER_TIME = "HH:mm:ss"; final String H = "-"; final String S = "/"; final String P = "."; /** * 非Jackson转换器 * * @param registry */ @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToDateConverter()); registry.addConverter(new StringToLocalTimeConverter()); registry.addConverter(new StringToLocalDateConverter()); registry.addConverter(new StringToLocalDateTimeConverter()); } class StringToDateConverter implements Converter<String, Date> { @Override public Date convert(String source) { SimpleDateFormat formatter; if (source.contains(H)) { formatter = new SimpleDateFormat(FORMATTER_DATE_TIME_H); } else if (source.contains(S)) { formatter = new SimpleDateFormat(FORMATTER_DATE_TIME_S); } else { formatter = new SimpleDateFormat(); } try { return formatter.parse(source); } catch (ParseException e) { throw new IllegalArgumentException(String.format( "Failed to parse Date value '%s': %s", source, ClassUtil.exceptionMessage(e))); } } } class StringToLocalTimeConverter implements Converter<String, LocalTime> { @Override public LocalTime convert(String source) { if (source.contains(P)) { return LocalTime.parse(source); } else { return LocalTime.parse(source, DateTimeFormatter.ofPattern(FORMATTER_TIME)); } } } class StringToLocalDateConverter implements Converter<String, LocalDate> { @Override public LocalDate convert(String source) { if (source.contains(H)) { return LocalDate.parse(source, DateTimeFormatter.ofPattern(FORMATTER_DATE_H)); } else if (source.contains(S)) { return LocalDate.parse(source, DateTimeFormatter.ofPattern(FORMATTER_DATE_S)); } else { return LocalDate.parse(source); } } } class StringToLocalDateTimeConverter implements Converter<String, LocalDateTime> { @Override public LocalDateTime convert(String source) { if (source.contains(H)) { return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(FORMATTER_DATE_TIME_H)); } else if (source.contains(S)) { return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(FORMATTER_DATE_TIME_S)); } else { return LocalDateTime.parse(source); } } } }