【jackson】jackson全局配置方式 因为JsonDeserializer全局配置优先级导致@JsonFormat失效的问题
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
文章目录
我们知道jackson的序列化 反序列化 配置方式较多本文介绍常见的几种。
当然本文重点难点是在于文章目录中 最后一种配置 通过JsonDeserializer方式 导致@JsonFormat失效的问题。
避开问题的方式有很多 一条路行不通就换一条路但总会有想解决问题的人 偏要一条路走到黑 一个程序员最无助 无非遇到bug 百度谷歌Stack Overflow都搜不到答案的时候, 希望本文可以帮助到有需要的同学。
局部配置直接在字段加@JsonFormat注解
比如LocalDate字段 只截取年月
@JsonFormat(pattern = "yyyy-MM")
private LocalDate month;
全局配置 yml/properties中配置
这种方式并不灵活 也不易排查而且会因为项目中实现了某接口导致某些配置失效的问题就不做描述了 了解有这种方式 且知道这种方式会有坑就行了。
全局配置 @Configuration配置(不影响@JsonFormat)
一个正常比较符合常理的设计 全局配置是不应该覆盖注解的
因为我们很可能出现特定场景用特定的格式的情况而这种配置 正好可以满足我们的需求
很不巧博主项目中不是用的这种方式 所以才有了这篇文章
@Configuration
public class LocalDateTimeSerializerConfig {
@Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
private String pattern;
@Bean
public LocalDateTimeSerializer localDateTimeDeserializer() {
return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern));
}
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> builder.serializerByType(LocalDateTime.class, localDateTimeDeserializer());
}
}
这种方式 LocalDateTime全局时间格式是yyyy-MM-dd HH:mm:ss
如果某个实体类中 需要特定格式 那就可以配合注解使用
@JsonFormat注解在这种配置下 优先级仍比较高会以@JsonFormat注解标注的时间格式为主。
如 @JsonFormat(pattern = “yyyy-MM-dd HH:mm”)
全局配置 继承JsonDeserializer配置(影响@JsonFormat) 并解决该问题重难点
这种方式会导致@JsonFormat失效 它优先级不再是最高
博主在网上并没找到如何解决该问题的提问与回答但博主比较固执 比较想解决这个问题 经过多次调试后 终于写出了解决方案
这种配置方式 有很多变种写法 , 首先是网上比较常见的 也是最简单的
它们本质都是一样的继承JsonDeserializer
@Configuration
public class LocalDateTimeSerializerConfig {
@Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
private String pattern;
@Bean
@Primary
public ObjectMapper serializingObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
public class LocalDateTimeSerializer extends JsonSerializer<localdatetime> {
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value.format(ofPattern(pattern)));
}
}
public class LocalDateTimeDeserializer extends JsonDeserializer<localdatetime> {
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException {
return LocalDateTime.parse(p.getValueAsString(), ofPattern(pattern));
}
}
}
还可以使用@JsonComponent注解和上面的类似 就不重复了。
再介绍一种博主目前项目中使用的方式 直接把工具类代码贴上会有其它代码
public class JacksonUtils {
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
private JacksonUtils() {
}
public final static ObjectMapper MAPPER;
static {
MAPPER = serializingObjectMapper();
}
public static String serialize(Object obj) {
try {
return MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
public static Object deserialize(String jsonText, TypeReference type) {
try {
return MAPPER.readValue(jsonText, type);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static <T> T deserialize(String jsonText, Class<T> beanClass) {
try {
return MAPPER.readValue(jsonText, beanClass);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static JsonNode deserialize(String jsonText) {
try {
return MAPPER.readTree(jsonText);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static JsonNode toJsonNode(Object obj) {
try {
return MAPPER.readTree(serialize(obj));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static ObjectNode createNode() {
try {
return MAPPER.createObjectNode();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static ArrayNode createArrayNode() {
try {
return MAPPER.createArrayNode();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static Map toMap(Object objEntity) {
if (Objects.isNull(objEntity)) {
return null;
}
Map map = new HashMap();
try {
map = MAPPER.readValue(MAPPER.writeValueAsString(objEntity), Map.class);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return map;
}
/**
* jackson2 json序列化 null字段输出为空串
*/
public static ObjectMapper serializingObjectMapper() {
//设置日期格式
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//序列化日期格式
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer());
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer());
javaTimeModule.addSerializer(Date.class, new DateSerializer(false, simpleDateFormat));
//反序列化日期格式
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer());
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer());
javaTimeModule.addDeserializer(Date.class, new JsonDeserializer<Date>() {
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String date = jsonParser.getText();
try {
return simpleDateFormat.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
});
objectMapper.registerModule(javaTimeModule)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
//序列化成json时将所有的Long变成string以解决js中的精度丢失。
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
//忽略不存在的字段
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
/**
* LocalDateTime序列化
*/
private static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value.format(DATETIME_FORMATTER));
}
}
/**
* LocalDateTime反序列化
*/
private static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return LocalDateTime.parse(p.getValueAsString(), DATETIME_FORMATTER);
}
}
/**
* LocalDate序列化
*/
private static class LocalDateSerializer extends JsonSerializer<LocalDate> {
@Override
public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 获取value来源的类
Class<?> aClass = gen.getCurrentValue().getClass();
// 获取字段名
String currentName = gen.getOutputContext().getCurrentName();
try {
// 获取字段
Field declaredField = aClass.getDeclaredField(currentName);
// 校验是否LocalDate属性的字段
if (Objects.equals(declaredField.getType(), LocalDate.class)) {
// 是否被@JsonFormat修饰
boolean annotationPresent = declaredField.isAnnotationPresent(JsonFormat.class);
if (annotationPresent) {
String pattern = declaredField.getAnnotation(JsonFormat.class).pattern();
if (StringUtils.isNotEmpty(pattern)) {
gen.writeString(value.format(DateTimeFormatter.ofPattern(pattern)));
} else {
gen.writeString(value.format(DATE_FORMATTER));
}
} else {
gen.writeString(value.format(DATE_FORMATTER));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* LocalDate反序列化
*/
private static class LocalDateDeserializer extends JsonDeserializer<LocalDate> {
@Override
public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return LocalDate.parse(p.getValueAsString(), DATE_FORMATTER);
}
}
/**
* LocalTime序列化
*/
private static class LocalTimeSerializer extends JsonSerializer<LocalTime> {
@Override
public void serialize(LocalTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value.format(TIME_FORMATTER));
}
}
/**
* LocalTime反序列化
*/
private static class LocalTimeDeserializer extends JsonDeserializer<LocalTime> {
@Override
public LocalTime deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
return LocalTime.parse(p.getValueAsString(), TIME_FORMATTER);
}
}
}
可以看到 上面是没有使用spring的@Configuration注解的 那么是如何实现注册的呢
实现WebMvcConfigurer 接口
public class SpringMvcBaseConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
converters.add(jackson2HttpMessageConverter());
}
/**
* 时间格式转换器,将Date类型统一转换为yyyy-MM-dd HH:mm:ss格式的字符串
* 长整型转换成String
* 忽略value为null时key的输出
*/
public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = JacksonUtils.serializingObjectMapper();
converter.setObjectMapper(objectMapper);
return converter;
}
}
我们解决方案的代码 就是这一段了
逻辑为
- 从JsonGenerator gen中 反射获取当前字段所在的类以及该字段的字段名。
- 有了类和字段名就可以获取到字段
- 有了字段 就可以获取到注解 以及注解里面的内容
- 获取到内容后判断不为空时使用注解里面的格式即可
/**
* LocalDate序列化
*/
private static class LocalDateSerializer extends JsonSerializer<LocalDate> {
@Override
public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 获取value来源的类
Class<?> aClass = gen.getCurrentValue().getClass();
// 获取字段名
String currentName = gen.getOutputContext().getCurrentName();
try {
// 获取字段
Field declaredField = aClass.getDeclaredField(currentName);
// 校验是否LocalDate属性的字段
//这个判断其实也可以不用 进入该方法中的字段 都是LocalDate类型的
//当然 因为反射时可以取任何字段 如写成了getDeclaredFields()
// 错误或恶意取到了其它字段 就会导致报错 多重校验也是没问题的
if (Objects.equals(declaredField.getType(), LocalDate.class)) {
// 是否被@JsonFormat修饰
boolean annotationPresent = declaredField.isAnnotationPresent(JsonFormat.class);
if (annotationPresent) {
String pattern = declaredField.getAnnotation(JsonFormat.class).pattern();
if (StringUtils.isNotEmpty(pattern)) {
gen.writeString(value.format(DateTimeFormatter.ofPattern(pattern)));
} else {
gen.writeString(value.format(DATE_FORMATTER));
}
} else {
gen.writeString(value.format(DATE_FORMATTER));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}