This is the third day of my participation in the August More text Challenge. For details, see: August More Text Challenge

preface

When we develop the API interface of the project, some fields without data will return NULL by default, and the numeric type will also be NULL. At this time, the front end expects that the string can return NULL characters, and the numeric type will return 0 by default, so we need to customize the JSON serialization processing

SpringBoot’s default JSON parsing scheme

We know that there is a default JSON parser in SpringBoot. The default JSON parsing framework used in Spring Boot is Jackson. If we open the spring-boot-starter-web dependency in pom.xml, we can see a spring-boot-starter-json dependency:

 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>2.4.7</version>
      <scope>compile</scope>
    </dependency>
Copy the code

Spring Boot encapsulates dependencies. You can see many spring-boot-starter- XXX dependencies. This is one of the features of Spring Boot, and there is no need to introduce many dependencies. The starter- XXX series directly contains the necessary dependencies, so we can click on the spring-boot-starter-json dependency again, and see:

 <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.11.4</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-jdk8</artifactId>
      <version>2.11.4</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-jsr310</artifactId>
      <version>2.11.4</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.module</groupId>
      <artifactId>jackson-module-parameter-names</artifactId>
      <version>2.11.4</version>
      <scope>compile</scope>
    </dependency>
Copy the code

When we return json from the controller, the @responseBody annotation automatically serializes the object returned by the server into a JSON string, The @requestBody annotation on the object parameter automatically helps us deserialize the json string from the front end into a Java object

These functions are implemented through the HttpMessageConverter message conversion utility class

SpringMVC automatically configures Jackson and Gson’s HttpMessageConverter, and SpringBoot does the same

JacksonHttpMessageConvertersConfiguration

org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(ObjectMapper.class)
	@ConditionalOnBean(ObjectMapper.class)
	@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "jackson", matchIfMissing = true)
	static class MappingJackson2HttpMessageConverterConfiguration {

		@Bean
		@ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class, ignoredType = { "org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" })
		MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
			return newMappingJackson2HttpMessageConverter(objectMapper); }}Copy the code

JacksonAutoConfiguration

org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration

@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
	static class JacksonObjectMapperConfiguration {

		@Bean
		@Primary
		@ConditionalOnMissingBean
		ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
			return builder.createXmlMapper(false).build(); }}Copy the code

Gson’s automatic configuration class

org.springframework.boot.autoconfigure.http.GsonHttpMessageConvertersConfiguration

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnBean(Gson.class)
	@Conditional(PreferGsonOrJacksonAndJsonbUnavailableCondition.class)
	static class GsonHttpMessageConverterConfiguration {

		@Bean
		@ConditionalOnMissingBean
		GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) {
			GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
			converter.setGson(gson);
			returnconverter; }}Copy the code

Customize SprinBoot JSON parsing

Date format parsing

The default timestamp format is returned, but the timestamp is one day shorter.

Spring. The datasource. Url = JDBC: p6spy: mysql: / / 47.100.78.146:3306 / mall? zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&autoReconnect=trueCopy the code
  1. use@JsonFormatAnnotate custom formats
	@JsonFormat(pattern = "yyyy-MM-dd")
    private Date birthday;
Copy the code

However, the need to add this annotation to every date field in an entity class is not flexible enough

  1. Global add

Add spring.jackson.date-format= YYYY-MM-dd to the configuration file

NULL fields are not returned

  1. A null field can be used in an interface if it is not necessary to return a null field@JsonIncludeannotations
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String title;
Copy the code

But the need to add this annotation to every field in an entity class is not flexible enough

  1. Global add Is added directly to the configuration filespring.jackson.default-property-inclusion=non_null

Custom field serialization

User-defined NULL String Field Returns a null character NullStringJsonSerializer Serializer

public class NullStringJsonSerializer extends JsonSerializer {
    @Override
    public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        if (o == null) {
            jsonGenerator.writeString(""); }}}Copy the code

Custom null field digital type returns 0, the default value NullIntegerJsonSerializer serialization

public class NullIntegerJsonSerializer extends JsonSerializer {
    @Override
    public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        if (o == null) {
            jsonGenerator.writeNumber(0); }}}Copy the code

Custom floating point decimal type 4 rounding 5 retaining 2 decimal digits DoubleJsonSerialize

public class DoubleJsonSerialize extends JsonSerializer {
    private DecimalFormat df = new DecimalFormat("00 # #.");

    @Override
    public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        if(value ! =null) {
            jsonGenerator.writeString(NumberUtil.roundStr(value.toString(), 2));
        }else{
            jsonGenerator.writeString("0.00"); }}}Copy the code

User-defined NullArrayJsonSerializer serializer

public class NullArrayJsonSerializer extends JsonSerializer {


    @Override
    public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        if(o==null){
            jsonGenerator.writeStartArray();
        }else{ jsonGenerator.writeObject(o); }}}Copy the code

The custom BeanSerializerModifier uses our own serializer for bean serialization

public class MyBeanSerializerModifier extends BeanSerializerModifier {

    private JsonSerializer _nullArrayJsonSerializer = new NullArrayJsonSerializer();

    private JsonSerializer _nullStringJsonSerializer = new NullStringJsonSerializer();

    private JsonSerializer _nullIntegerJsonSerializer = new NullIntegerJsonSerializer();

    private JsonSerializer _doubleJsonSerializer = new DoubleJsonSerialize();

    @Override
    public List changeProperties(SerializationConfig config, BeanDescription beanDesc, List beanProperties) { // Loop through all BeanPropertyWriters
        for (int i = 0; i < beanProperties.size(); i++) {
            BeanPropertyWriter writer = (BeanPropertyWriter) beanProperties.get(i);
            // Determine the field type. If the field type is Array, List, or SET, register nullSerializer
            if (isArrayType(writer)) { // Register a nullSerializer for the Writer
                writer.assignNullSerializer(this.defaultNullArrayJsonSerializer());
            }
            if (isStringType(writer)) {
                writer.assignNullSerializer(this.defaultNullStringJsonSerializer());
            }
            if (isIntegerType(writer)) {
                writer.assignNullSerializer(this.defaultNullIntegerJsonSerializer());
            }
            if (isDoubleType(writer)) {
                writer.assignSerializer(this.defaultDoubleJsonSerializer()); }}return beanProperties;
    } // What type is the judgment

    protected boolean isArrayType(BeanPropertyWriter writer) {
        Class clazz = writer.getPropertyType();
        return clazz.isArray() || clazz.equals(List.class) || clazz.equals(Set.class);
    }

    protected boolean isStringType(BeanPropertyWriter writer) {
        Class clazz = writer.getPropertyType();
        return clazz.equals(String.class);
    }

    protected boolean isIntegerType(BeanPropertyWriter writer) {
        Class clazz = writer.getPropertyType();
        return clazz.equals(Integer.class) || clazz.equals(int.class) || clazz.equals(Long.class);
    }

    protected boolean isDoubleType(BeanPropertyWriter writer) {
        Class clazz = writer.getPropertyType();
        return clazz.equals(Double.class) || clazz.equals(BigDecimal.class);
    }


    protected JsonSerializer defaultNullArrayJsonSerializer(a) {
        return _nullArrayJsonSerializer;
    }

    protected JsonSerializer defaultNullStringJsonSerializer(a) {
        return _nullStringJsonSerializer;
    }

    protected JsonSerializer defaultNullIntegerJsonSerializer(a) {
        return _nullIntegerJsonSerializer;
    }

    protected JsonSerializer defaultDoubleJsonSerializer(a) {
        return_doubleJsonSerializer; }}Copy the code

The effective application of our own bean serialization make MappingJackson2HttpMessageConverter class in the configuration class provides MappingJackson2HttpMessageConverter class, Use ObjectMapper for global serialization

@Configuration
public class ClassJsonConfiguration {
    @Bean
    public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter(a) {
        final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();

        ObjectMapper mapper = converter.getObjectMapper();

        // For mapper to register a Factory with SerializerModifier, the main thing to do is: judge serialization type, according to the type specified as null when the value

        mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));

        returnconverter; }}Copy the code

This class replaces SpringBoot’s default JSON parsing scheme. In fact, it’s the ObjectMapper class that matters in this class, so you can configure this class directly as well.

 @Bean
    public ObjectMapper om(a) {
        ObjectMapper mapper = new ObjectMapper();
        // For mapper to register a Factory with SerializerModifier, the main thing to do is: judge serialization type, according to the type specified as null when the value

        mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
        return mapper;
    }
Copy the code

You can also customize the serialization using the @jsonSerialize annotation. For example:

@Component
public class DoubleSerialize extends JsonSerializer<Double> {
 
    private DecimalFormat df = new DecimalFormat("00 # #.");  
 
    @Override
    public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers)
            throws IOException, JsonProcessingException {
        if(value ! =null) { gen.writeString(df.format(value)); }}}Copy the code

And then you need to use the field above

 @JsonSerialize(using = DoubleJsonSerialize.class)
    private BigDecimal price;

Copy the code

Configuration file Jackson Detailed configuration

  spring:
    jackson:
      SNAKE_CASE- return json camelback to underline, json body underline to the back end automatically to camel
      property-naming-strategy: SNAKE_CASE
      # Global set @jsonFormat format pattern
      date-format: yyyy-MM-dd HH:mm:ss
      # Local time zone
      locale: zh
      Set the global time zone
      time-zone: GMT+8
      Set the serialization mode of poJOs or attributes annotated by @jsonInclude globally
      default-property-inclusion: NON_NULL Attributes that are not empty will be serialized. For details, see jsoninclude.include
      By default, the enumeration attribute in the SerializationFeature class is key, and the value is Boolean to set the Jackson SerializationFeature
      serialization:
        WRITE_DATES_AS_TIMESTAMPS: true # Convert java.util.date returned to timestamp
        FAIL_ON_EMPTY_BEANS: true # Whether an error will be reported when the object is empty. Default is true
      # Set Jackson DeserializationFeature to DeserializationFeature to DeserializationFeature to DeserializationFeature to DeserializationFeature to DeserializationFeature to DeserializationFeature
      deserialization:
        The default value is true when a poJO does not exist in json
        FAIL_ON_UNKNOWN_PROPERTIES: false
      Set Jackson ObjectMapper feature to key with Boolean value
      # ObjectMapper is responsible for json read/write, JSON poJO transfer, json tree transfer, see MapperFeature, default
      mapper:
        # Use getName() instead of setName(). For example, if the class contains getName() but does not contain the name property and setName(), the vo JSON format template will still contain the name property
        USE_GETTERS_AS_SETTERS: true # the default false
      The enumeration attribute in the enumeration class is key and the value is Boolean to set the Jackson JsonParser Feature
      # JsonParser is responsible for reading JSON content in Jackson. For details, see JsonParser
      parser:
        ALLOW_SINGLE_QUOTES: true # Whether to allow single quotes. Default: false
      Set Jackson JsonGenerator to the key attribute of the enumeration class. The value is Boolean
      # JsonGenerator is responsible for writing json content in Jackson. For details, see JsonGenerator
 

Copy the code