springboot time formatting and null value formatting

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.
insert image description here

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;

insert image description here

@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

insert image description here

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.

insert image description here
That is, the LocalDateSerializer and LocalDateTimeSerializer have been changed:

insert image description here

insert image description here

1.1.2 Global configuration

Comment out @JsomFormat:
insert image description here

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.
insert image description here
Because the LocalDateSerializer and LocalDateTimeSerializer have not been changed, the result is only valid for the date.
insert image description here

1.1.2.2 Write a configuration class for global configuration (recommended)

First cancel the configuration of the previous step
insert image description here

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:
insert image description here
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:
insert image description here
remove the annotation:

insert image description here

insert image description here
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;
    }

insert image description here
insert image description here
Because the default format templates of LocalDateTimeDeserializer and LocalDateDeserializer are 'yyyy-MM-ddTHH:mm:ss and 'yyyy-MM-dd' respectively. It is no
insert image description here
insert image description here
problem for us to transfer according to the default format:
insert image description here
insert image description here

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.)
insert image description here

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:
insert image description here
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日")));
        };
    }

insert image description here
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;
    }
    

insert image description here

insert image description here
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:
insert image description here
Non-json parameters are converted by the GenericConversionService class. If the @DateTimeFormat annotation is marked, Jsr310DateTimeFormatAnnotationFormatterFactory will return a new parsing class object:
insert image description here

FormattingConversionService as an implementation class will generate and convert GenericConverter through Parser class objects:
insert image description here

1.2.2.2 Global configuration

Remove the annotation on the field:
insert image description here
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:
insert image description here

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;
    }


insert image description here
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:
insert image description here
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:

insert image description here
After restarting:
insert image description here
insert image description here
we restore the code, check the results when it takes effect, and find that the time class is not in the format we defined:
insert image description here
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:
insert image description here

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:
insert image description here
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):
insert image description here
adjust the parameter test interface to json type:
insert image description here
conversion failure:
insert image description here
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:
insert image description here
insert image description here

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());
    }
}

Guess you like

Origin blog.csdn.net/worilb/article/details/128686586