Table of contents
1 time formatting
1.1 Output parameter format (Json)
Create a test class:
@Data
public class TestResponse {
@ApiModelProperty("未格式化时间")
private LocalDateTime localDateTime;
@ApiModelProperty("未格式化时间")
private LocalDate localDate;
@ApiModelProperty("未格式化时间")
private Date date;
}
restApi:
@GetMapping("test")
@ApiOperation("响应测试")
public TestResponse test(){
TestResponse response = new TestResponse();
response.setDate(new Date());
response.setLocalDateTime(LocalDateTime.now());
response.setLocalDate(LocalDate.now());
return response;
}
The response parameter with Time adopts the "yyyy-MM-dd'T'HH:mm:ss.SSS" format, and DATE uses UTC time by default, which is slower than Beijing time.
1.1.1 Partial configuration
Add annotation @JsonFormat to the field
@ApiModelProperty("已格式化时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;
@ApiModelProperty("已格式化时间")
@JsonFormat(pattern = "yyyy年MM月dd日")
private LocalDate localDate;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") //date默认使用UTC时间,北京时间得+8
@ApiModelProperty("已格式化时间")
private Date date;
@JsonFormat is jakson's own annotation (the spring family uses jakson for serialization and deserialization by default), and the common attributes are as follows:
Attributes | effect |
---|---|
pattern | Format template, such as 'yyyy-MM-dd HH:mm:ss of time |
shape | Determine the data type, such as NUMBER, ARRAY, the default is ANY, any type |
locale | Locales |
timezone | time zone, default is UTC |
The serializer will be checked in the createContextual method of the JSR310FormattedSerializerBase class. First, the JsonFormat on the target object field will be obtained. If the JsonFormat is not empty, its attribute value will be read. Create a DateTimeFormatter object from the property value and set it as the serializer template.
Finally, the subclass MappingJackson2HttpMessageConverter of the class implemented through the HttpMessageConverter interface outputs the response. MappingJackson2HttpMessageConverter is the default Json conversion class.
That is, the LocalDateSerializer and LocalDateTimeSerializer have been changed:
1.1.2 Global configuration
Comment out @JsomFormat:
1.1.2.1 Global configuration in yml configuration file
spring:
jackson:
# 全局配置,但是对LocalDateTime和LocalDate无效
date-format: yyyy-MM-dd HH:mm:ss
# 时区
time-zone: GMT+8
Restart, you can see that the format and time zone we set in the configuration file are injected into the ObjectMapper.
Because the LocalDateSerializer and LocalDateTimeSerializer have not been changed, the result is only valid for the date.
1.1.2.2 Write a configuration class for global configuration (recommended)
First cancel the configuration of the previous step
Handwritten ObjectMapper configuration class:
@Configuration
public class DateTimeConfiguration {
@Bean
public ObjectMapper initObjectMapper(){
ObjectMapper objectMapper=new ObjectMapper();
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//JavaTimeModule 是jackson通过jsr310引入的JAVA8格式化类,没有此Module,序列化后的值会比较乱
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
}
The running result is the same as the previous YML configuration:
so LocalDateSerializer and LocalDateTimeSerializer must also be serialized in the format we need:
@Bean
public ObjectMapper initObjectMapper(){
//自定义ObjectMapper配置并注册BEAN
ObjectMapper objectMapper=new ObjectMapper();
JavaTimeModule javaTimeModule=new JavaTimeModule();
//针对LocalDateTime和LocalDate
javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
//针对Date类
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//JavaTimeModule 是jackson通过jsr310引入的JAVA8格式化类,没有此Module,序列化后的值会比较乱
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
The above localdate field is not covered by the global serialization, because the field is annotated, indicating that the local has priority over the global:
remove the annotation:
You can also use Jackson2ObjectMapperBuilder to build a new ObjectMapper instance and set the serialization and deserialization methods:
@Configuration
public class DateTimeConfiguration {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
//通过ObjectMapper构造器构建实例
return builder -> {
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //date格式,序列化和反序列化都是它
//序列化
builder.serializerByType(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
builder.serializerByType(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
//反序列化
builder.deserializerByType(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
builder.deserializerByType(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
};
}
// @Bean
// public ObjectMapper initObjectMapper(){ //自定义ObjectMapper配置并注册BEAN
// ObjectMapper objectMapper=new ObjectMapper();
// JavaTimeModule javaTimeModule=new JavaTimeModule();
// //针对LocalDateTime和LocalDate
// javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// //针对Date类
// objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// //JavaTimeModule 是jackson通过jsr310引入的JAVA8格式化类,没有此Module,序列化后的值会比较乱
// objectMapper.registerModule(javaTimeModule);
// return objectMapper;
// }
}
1.2 Input parameter format
1.2.1 JSON parameters
Create a test interface
@PostMapping("body")
@ApiOperation("json请求测试")
public TestResponse body(@RequestBody TestResponse testResponse){
log.info("打印的body:{}",testResponse);
return testResponse;
}
Because the default format templates of LocalDateTimeDeserializer and LocalDateDeserializer are 'yyyy-MM-ddTHH:mm:ss and 'yyyy-MM-dd' respectively. It is no
problem for us to transfer according to the default format:
1.2.1.1 Partial configuration
Similarly, we can deserialize events locally as well as serialize them locally.
Still use the @JsonFormat annotation (serialization and deserialization are consistent), at this time, the global serialization configuration of the marked annotation field is invalid:
@ApiModelProperty("已格式化时间")
@JsonFormat(pattern = "yyyy年MM月dd日HH时mm分ss秒")
private LocalDateTime localDateTime;
@ApiModelProperty("已格式化时间")
@JsonFormat(pattern = "yyyy年MM月dd日")
private LocalDate localDate;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS") //date默认使用UTC时间,北京时间得+8
@ApiModelProperty("已格式化时间")
private Date date;
Same as serialization, when JSR310DateTimeDeserializerBase deserializes, it also reads the attribute value in the annotation and creates a new JsonDeserializer (including LocalDateTimeDeserializer, LocalDateDeserializer, etc.)
1.2.1.2 Global configuration
We use the global configuration for serialization, and the deserialization should also use the global configuration.
Remove the annotation:
and then add the deserialization method to the existing global configuration:
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //date格式,序列化和反序列化都是它
//序列化
builder.serializerByType(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
builder.serializerByType(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
//反序列化
builder.deserializerByType(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
builder.deserializerByType(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
};
}
Naturally, you can also customize and register ObjectMapper to BEAN
@Bean
public ObjectMapper initObjectMapper(){
//自定义ObjectMapper配置并注册BEAN
ObjectMapper objectMapper=new ObjectMapper();
JavaTimeModule javaTimeModule=new JavaTimeModule();
//针对LocalDateTime和LocalDate
//序列化
javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
//反序列化
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
//针对Date类 序列化和反序列化都是它
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//JavaTimeModule 是jackson通过jsr310引入的JAVA8格式化类,没有此Module,序列化后的值会比较乱
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
// @Bean
// public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
// return builder -> {
// builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //date格式,序列化和反序列化都是它
// //序列化
// builder.serializerByType(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
// builder.serializerByType(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
// //反序列化
// builder.deserializerByType(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
// builder.deserializerByType(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
// };
// }
1.2.2 Non-json parameters
Change the interface parameter to a non-JSON type
@PostMapping("param")
@ApiOperation("普通参数请求测试")
public TestResponse body(TestResponse testResponse){
try {
log.info("打印的param:{}",testResponse);
} catch (Exception e) {
e.printStackTrace();
}
return testResponse;
}
At this time, the json deserialization method we configured is invalid
1.2.2.1 Partial configuration
We add the annotation @DateTimeFormat to the field
@ApiModelProperty("已格式化时间")
@DateTimeFormat(pattern = "yyyy年MM月dd日HH时mm分ss秒")
private LocalDateTime localDateTime;
@ApiModelProperty("已格式化时间")
@DateTimeFormat(pattern = "yyyy年MM月dd日")
private LocalDate localDate;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
@ApiModelProperty("已格式化时间")
private Date date;
Input parameter test according to the corresponding format:
Non-json parameters are converted by the GenericConversionService class. If the @DateTimeFormat annotation is marked, Jsr310DateTimeFormatAnnotationFormatterFactory will return a new parsing class object:
FormattingConversionService as an implementation class will generate and convert GenericConverter through Parser class objects:
1.2.2.2 Global configuration
Remove the annotation on the field:
add the following code to the time configuration class:
/**
* string to date
* @return date
*/
@Bean
public DateConverter dateConverter () {
return new DateConverter();
}
/**
* string to localDate
* @return localDate
*/
@Bean
public LocalDateConverter localDateConverter () {
return new LocalDateConverter();
}
/**
* string to LocalDateTime
* @return LocalDateTime
*/
@Bean
public LocalDateTimeConverter localDateTimeConverter () {
return new LocalDateTimeConverter();
}
static class DateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
String pattern ;
if (source.length() <= "yyyy-MM-dd".length()) {
pattern = "yyyy-MM-dd";
}else {
pattern = "yyyy-MM-dd HH:mm:ss.SSS";
}
SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
try {
return dateFormat.parse(source);
} catch (ParseException e) {
throw new RuntimeException(source + " to date error!");
}
}
}
static class LocalDateConverter implements Converter<String, LocalDate> {
@Override
public LocalDate convert(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
}
}
static class LocalDateTimeConverter implements Converter<String, LocalDateTime> {
@Override
public LocalDateTime convert(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
return LocalDateTime.parse(source, DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒"));
}
}
Test Results:
The final code of the configuration class:
package com.example.demo.config;
import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.std.NullSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.DurationSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.convert.converter.Converter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.TimeZone;
@Configuration
public class DateTimeConfiguration {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //date格式,序列化和反序列化都是它
//序列化
builder.serializerByType(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
builder.serializerByType(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
//反序列化
builder.deserializerByType(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
builder.deserializerByType(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
};
}
// @Bean
// public ObjectMapper initObjectMapper(){ //自定义ObjectMapper配置并注册BEAN
// ObjectMapper objectMapper=new ObjectMapper();
// JavaTimeModule javaTimeModule=new JavaTimeModule();
// //针对LocalDateTime和LocalDate
// //序列化
// javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
// //反序列化
// javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
// javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// //针对Date类 序列化和反序列化都是它
// objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// //JavaTimeModule 是jackson通过jsr310引入的JAVA8格式化类,没有此Module,序列化后的值会比较乱
// objectMapper.registerModule(javaTimeModule);
// return objectMapper;
// }
//以下针对非JSON参数
/**
* string to date
* @return date
*/
@Bean
public DateConverter dateConverter () {
return new DateConverter();
}
/**
* string to localDate
* @return localDate
*/
@Bean
public LocalDateConverter localDateConverter () {
return new LocalDateConverter();
}
/**
* string to LocalDateTime
* @return LocalDateTime
*/
@Bean
public LocalDateTimeConverter localDateTimeConverter () {
return new LocalDateTimeConverter();
}
static class DateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
String pattern ;
if (source.length() <= "yyyy-MM-dd".length()) {
pattern = "yyyy-MM-dd";
}else {
pattern = "yyyy-MM-dd HH:mm:ss.SSS";
}
SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
try {
return dateFormat.parse(source);
} catch (ParseException e) {
throw new RuntimeException(source + " to date error!");
}
}
}
static class LocalDateConverter implements Converter<String, LocalDate> {
@Override
public LocalDate convert(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
}
}
static class LocalDateTimeConverter implements Converter<String, LocalDateTime> {
@Override
public LocalDateTime convert(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
return LocalDateTime.parse(source, DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒"));
}
}
}
2 Null value formatting
2.1 Response data null value formatting
The various types of data we return to the front-end may be empty. At this time, we usually return NULL. Sometimes the front-end feels that this is not friendly enough.
If you want to convert, generally the string is converted to an empty string "", the collection is converted to an empty array [], the number is converted to 0, and the Boolean is converted to false. In this paper, we will achieve this goal. Of course, the most important thing is to make an agreement with the front end, how convenient it is, the standard is determined by people after all.
Add some fields to the entity class:
@Data
public class TestResponse {
@ApiModelProperty("字符串为NULL")
private String str;
@ApiModelProperty("已格式化时间")
// @DateTimeFormat(pattern = "yyyy年MM月dd日HH时mm分ss秒")
private LocalDateTime localDateTime;
@ApiModelProperty("已格式化时间")
// @DateTimeFormat(pattern = "yyyy年MM月dd日")
private LocalDate localDate;
// @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
@ApiModelProperty("已格式化时间")
private Date date;
@ApiModelProperty("集合为NULL")
private List<String> list;
@ApiModelProperty("布尔型为NULL")
private Boolean bool;
@ApiModelProperty("数字型为NULL")
private Integer anInt;
@ApiModelProperty("LocalDateTime为NULL")
private LocalDateTime nullLocalDateTime;
@ApiModelProperty("LocalDate为NULL")
private LocalDate nullLocalDate;
@ApiModelProperty("Date为NULL")
private Date nullDate;
}
Still use the previous test interface, and still only assign values to a few old fields.
@GetMapping("test")
@ApiOperation("响应测试")
public TestResponse test(){
TestResponse response = new TestResponse();
response.setDate(new Date());
response.setLocalDateTime(LocalDateTime.now());
response.setLocalDate(LocalDate.now());
return response;
}
You can add annotations or write configuration classes to ignore null fields, but this is beyond the scope of this article.
We can implement WebMvcConfigurer and rewrite extendMessageConverters, change the HttpMessageConverter list, and achieve the purpose of custom message conversion:
@Configuration
@Slf4j
public class MvcConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//fastJson写法
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(
SerializerFeature.WriteNullStringAsEmpty, // 将String类型的字段如果为null,输出为"",而非null
SerializerFeature.WriteNullNumberAsZero, // 将Number类型的字段如果为null,输出为0,而非null
SerializerFeature.WriteNullListAsEmpty, // 将List类型的字段如果为null,输出为[],而非null
SerializerFeature.WriteNullBooleanAsFalse, // 将Boolean类型的字段如果为null,输出为false,而非null
SerializerFeature.DisableCircularReferenceDetect // 避免循环引用
);
fastConverter.setFastJsonConfig(fastJsonConfig);
converters.add(0, fastConverter);
}
}
Test results:
It can be seen that except for the time class, all Null fields have been converted into our custom format.
Converters represents the HttpMessageConverter list, and the element MappingJackson2HttpMessageConverter as the AbstractJackson2HttpMessageConverter subclass is the default json converter in spring. Our new FastJsonHttpMessageConverter is also a subclass of AbstractJackson2HttpMessageConverter, and it is ranked before the default MappingJackson2HttpMessageConverter, so it replaces MappingJackson2HttpMessageConverter. If it is after MappingJackson2HttpMessageConverter, it is useless:
After restarting:
we restore the code, check the results when it takes effect, and find that the time class is not in the format we defined:
because we use fastjson instead of the system default Jackson. Although fastjson is efficient, it is not suitable for Jackson here.
Here, using the writing method seen on the Internet, custom MappingJackson2HttpMessageConverter inherits the class message converter, inherits BeanSerializerModifier internally, and rewrites the changeProperties method (emphasis):
custom message converter
class JacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {
/**
* 处理数组类型的null值
*/
public class NullArrayJsonSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
if (value == null) {
jgen.writeStartArray();
jgen.writeEndArray();
}
}
}
/**
* 处理字符串类型的null值
*/
public class NullStringJsonSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(StringUtils.EMPTY);
}
}
/**
* 处理数字类型的null值
*/
public class NullNumberJsonSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeNumber(0);
}
}
/**
* 处理布尔类型的null值
*/
public class NullBooleanJsonSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeBoolean(false);
}
}
public class MyBeanSerializerModifier extends BeanSerializerModifier {
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
//循环所有的beanPropertyWriter
for (Object beanProperty : beanProperties) {
BeanPropertyWriter writer = (BeanPropertyWriter) beanProperty;
//判断字段的类型,如果是array,list,set则注册nullSerializer
if (isArrayType(writer)) {
//给writer注册一个自己的nullSerializer
writer.assignNullSerializer(new NullArrayJsonSerializer());
} else if (isNumberType(writer)) {
writer.assignNullSerializer(new NullNumberJsonSerializer());
} else if (isBooleanType(writer)) {
writer.assignNullSerializer(new NullBooleanJsonSerializer());
} else if (isStringType(writer)) {
writer.assignNullSerializer(new NullStringJsonSerializer());
} else if (isDateType(writer)){
writer.assignNullSerializer(new NullStringJsonSerializer());
}
else if (isLcDateTimeType(writer)) {
writer.assignNullSerializer(new NullStringJsonSerializer());
}else if (isLcDateType(writer)) {
writer.assignNullSerializer(new NullStringJsonSerializer());
}
}
return beanProperties;
}
/**
* 是否是数组
*/
private boolean isArrayType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return clazz.isArray() || Collection.class.isAssignableFrom(clazz);
}
/**
* 是否是string
*/
private boolean isStringType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return CharSequence.class.isAssignableFrom(clazz) || Character.class.isAssignableFrom(clazz);
}
/**
* 是否是int
*/
private boolean isNumberType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return Number.class.isAssignableFrom(clazz);
}
/**
* 是否是boolean
*/
private boolean isBooleanType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return clazz.equals(Boolean.class);
}
/**
* 是否是LocalDate
*/
private boolean isDateType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return clazz.equals(Date.class);
}
/**
* 是否是LocalDate
*/
private boolean isLcDateType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return clazz.equals(LocalDate.class);
}
/**
* 是否是LocalDateTime
*/
private boolean isLcDateTimeType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return clazz.equals(LocalDateTime.class);
}
}
//自定义转换会让全局LocalDateTime序列化失效,默认转Long型
JacksonHttpMessageConverter() {
getObjectMapper()
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))//只对Date生效
.setSerializerFactory(getObjectMapper().getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
}
}
Add a custom converter first in the list of converters:
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0,new JacksonHttpMessageConverter());
}
The time format is still not as expected:
2.2 Time serialization failure problem after custom converter
Add the following code to the custom converter:
/**
* 处理LocalDateTime类型的值
*/
public class LocalDateTimeSerializer extends JsonSerializer<Object> {
private final DateTimeFormatter formatter= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(formatter.format((LocalDateTime) o));
}
}
/**
* 处理LocalDateTime类型的值
*/
public class LocalDateSerializer extends JsonSerializer<Object> {
private final DateTimeFormatter formatter= DateTimeFormatter.ofPattern("yyyy年MM月dd日");
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(formatter.format((LocalDate) o));
}
}
Modify the changeProperties method of the inner class MyBeanSerializerModifier, mainly to add the serialization method defined above:
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
//循环所有的beanPropertyWriter
for (Object beanProperty : beanProperties) {
BeanPropertyWriter writer = (BeanPropertyWriter) beanProperty;
//判断字段的类型,如果是array,list,set则注册nullSerializer
if (isArrayType(writer)) {
//给writer注册一个自己的nullSerializer
writer.assignNullSerializer(new NullArrayJsonSerializer());
} else if (isNumberType(writer)) {
writer.assignNullSerializer(new NullNumberJsonSerializer());
} else if (isBooleanType(writer)) {
writer.assignNullSerializer(new NullBooleanJsonSerializer());
} else if (isStringType(writer)) {
writer.assignNullSerializer(new NullStringJsonSerializer());
} else if (isDateType(writer)){
writer.assignNullSerializer(new NullStringJsonSerializer());
}
else if (isLcDateTimeType(writer)) {
//非空使用自定义序列化方式
writer.assignSerializer(new LocalDateTimeSerializer());
writer.assignNullSerializer(new NullStringJsonSerializer());
}else if (isLcDateType(writer)) {
//非空使用自定义序列化方式
writer.assignSerializer(new LocalDateSerializer());
writer.assignNullSerializer(new NullStringJsonSerializer());
}
}
return beanProperties;
}
The result is consistent:
you're done, right? It's not that simple. After customizing the converter, the time serialization and deserialization configurations will be invalid (using the default serialization format), and we only configured the sequence just now, and did not configure the deserialization.
Look at the deserialization method we configured before (JSON type):
adjust the parameter test interface to json type:
conversion failure:
indicating that the json input parameter must also be reserialized
2.3 Time deserialization failure problem after custom converter
We changed the serialization format by rewriting BeanSerializerModifier before, and now we also change the deserialization format by rewriting BeanDeserializerModifier.
Modify the code:
//反序列化修改器
public class MyBeanDeSerializerModifier extends BeanDeserializerModifier {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
Class<?> aClass = deserializer.handledType();
if (aClass.equals(LocalDateTime.class)) {
deserializer=new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
}
if (aClass.equals(LocalDate.class)) {
deserializer=new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
}
return deserializer;
}
}
//自定义转换会让全局LocalDateTime序列化失效,默认转Long型
JacksonHttpMessageConverter() {
DeserializerFactory dFactory = BeanDeserializerFactory.instance.withDeserializerModifier(new MyBeanDeSerializerModifier());
ObjectMapper objectMapper = new ObjectMapper(null, null, new DefaultDeserializationContext.Impl(dFactory));
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))//只对Date生效
.setSerializerFactory(getObjectMapper().getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
setObjectMapper(objectMapper);
}
Test Results:
The final code is as follows:
Time input parameter formatting configuration
@Configuration
public class DateTimeConfiguration {
// @Bean
// public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { //时间序列化在
// return builder -> {
// builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //date格式,序列化和反序列化都是它
// //序列化
// builder.serializerByType(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
// builder.serializerByType(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
// //反序列化
// builder.deserializerByType(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
// builder.deserializerByType(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
// };
// }
// @Bean
// public ObjectMapper initObjectMapper(){ //自定义ObjectMapper配置并注册BEAN
// ObjectMapper objectMapper=new ObjectMapper();
// JavaTimeModule javaTimeModule=new JavaTimeModule();
// //针对LocalDateTime和LocalDate
// //序列化
// javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
// //反序列化
// javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
// javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// //针对Date类 序列化和反序列化都是它
// objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// //JavaTimeModule 是jackson通过jsr310引入的JAVA8格式化类,没有此Module,序列化后的值会比较乱
// objectMapper.registerModule(javaTimeModule);
// return objectMapper;
// }
//以下针对非JSON参数
/**
* string to date
* @return date
*/
@Bean
public DateConverter dateConverter () {
return new DateConverter();
}
/**
* string to localDate
* @return localDate
*/
@Bean
public LocalDateConverter localDateConverter () {
return new LocalDateConverter();
}
/**
* string to LocalDateTime
* @return LocalDateTime
*/
@Bean
public LocalDateTimeConverter localDateTimeConverter () {
return new LocalDateTimeConverter();
}
static class DateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
String pattern ;
if (source.length() <= "yyyy-MM-dd".length()) {
pattern = "yyyy-MM-dd";
}else {
pattern = "yyyy-MM-dd HH:mm:ss.SSS";
}
SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
try {
return dateFormat.parse(source);
} catch (ParseException e) {
throw new RuntimeException(source + " to date error!");
}
}
}
static class LocalDateConverter implements Converter<String, LocalDate> {
@Override
public LocalDate convert(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
}
}
static class LocalDateTimeConverter implements Converter<String, LocalDateTime> {
@Override
public LocalDateTime convert(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
return LocalDateTime.parse(source, DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒"));
}
}
Custom message conversion
class JacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {
/**
* 处理数组类型的null值
*/
public class NullArrayJsonSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
if (value == null) {
jgen.writeStartArray();
jgen.writeEndArray();
}
}
}
/**
* 处理字符串类型的null值
*/
public class NullStringJsonSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(StringUtils.EMPTY);
}
}
/**
* 处理数字类型的null值
*/
public class NullNumberJsonSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeNumber(0);
}
}
/**
* 处理布尔类型的null值
*/
public class NullBooleanJsonSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeBoolean(false);
}
}
/**
* 处理LocalDateTime类型的值
*/
public class LocalDateTimeSerializer extends JsonSerializer<Object> {
private final DateTimeFormatter formatter= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(formatter.format((LocalDateTime) o));
}
}
/**
* 处理LocalDateTime类型的值
*/
public class LocalDateSerializer extends JsonSerializer<Object> {
private final DateTimeFormatter formatter= DateTimeFormatter.ofPattern("yyyy年MM月dd日");
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(formatter.format((LocalDate) o));
}
}
//序列化修改器
public class MyBeanSerializerModifier extends BeanSerializerModifier {
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
//循环所有的beanPropertyWriter
for (Object beanProperty : beanProperties) {
BeanPropertyWriter writer = (BeanPropertyWriter) beanProperty;
//判断字段的类型,如果是array,list,set则注册nullSerializer
if (isArrayType(writer)) {
//给writer注册一个自己的nullSerializer
writer.assignNullSerializer(new NullArrayJsonSerializer());
} else if (isNumberType(writer)) {
writer.assignNullSerializer(new NullNumberJsonSerializer());
} else if (isBooleanType(writer)) {
writer.assignNullSerializer(new NullBooleanJsonSerializer());
} else if (isStringType(writer)) {
writer.assignNullSerializer(new NullStringJsonSerializer());
} else if (isDateType(writer)){
writer.assignNullSerializer(new NullStringJsonSerializer());
}
else if (isLcDateTimeType(writer)) {
//非空使用自定义序列化方式
writer.assignSerializer(new LocalDateTimeSerializer());
writer.assignNullSerializer(new NullStringJsonSerializer());
}else if (isLcDateType(writer)) {
//非空使用自定义序列化方式
writer.assignSerializer(new LocalDateSerializer());
writer.assignNullSerializer(new NullStringJsonSerializer());
}
}
return beanProperties;
}
/**
* 是否是数组
*/
private boolean isArrayType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return clazz.isArray() || Collection.class.isAssignableFrom(clazz);
}
/**
* 是否是string
*/
private boolean isStringType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return CharSequence.class.isAssignableFrom(clazz) || Character.class.isAssignableFrom(clazz);
}
/**
* 是否是int
*/
private boolean isNumberType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return Number.class.isAssignableFrom(clazz);
}
/**
* 是否是boolean
*/
private boolean isBooleanType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return clazz.equals(Boolean.class);
}
/**
* 是否是LocalDate
*/
private boolean isDateType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return clazz.equals(Date.class);
}
/**
* 是否是LocalDate
*/
private boolean isLcDateType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return clazz.equals(LocalDate.class);
}
/**
* 是否是LocalDateTime
*/
private boolean isLcDateTimeType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getType().getRawClass();
return clazz.equals(LocalDateTime.class);
}
}
//反序列化修改器
public class MyBeanDeSerializerModifier extends BeanDeserializerModifier {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
Class<?> aClass = deserializer.handledType();
if (aClass.equals(LocalDateTime.class)) {
deserializer=new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
}
if (aClass.equals(LocalDate.class)) {
deserializer=new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
}
return deserializer;
}
}
//自定义转换会让全局LocalDateTime序列化失效,默认转Long型
JacksonHttpMessageConverter() {
DeserializerFactory dFactory = BeanDeserializerFactory.instance.withDeserializerModifier(new MyBeanDeSerializerModifier());
ObjectMapper objectMapper = new ObjectMapper(null, null, new DefaultDeserializationContext.Impl(dFactory));
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))//只对Date生效
.setSerializerFactory(getObjectMapper().getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
setObjectMapper(objectMapper);
Configure the message converter
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0,new JacksonHttpMessageConverter());
}
}