SpringBoot default json parser details and field serialization customization

foreword

When we develop the API interface of the project, some fields without data will return NULL by default, and the number type will also be NULL. At this time, the front end hopes that the string can return a null character uniformly, and the number returns 0 by default. Then we need to customize json Serialization

SpringBoot's default json parsing scheme

We know that there is a default json parser in springboot, and the Json parsing technology framework used by default in Spring Boot is jackson. We click on the dependency in pom.xml spring-boot-starter-web, and we can see a spring-boot-starter-jsondependency:

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

Dependencies are well encapsulated in Spring Boot. You can see many dependencies of the spring-boot-starter-xxx series. This is one of the characteristics of Spring Boot. There is no need to manually introduce many related dependencies. starter- The xxx series directly contain the necessary dependencies, so we click on the above spring-boot-starter-jsondependency again, and we can 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>

When we return json in the controller, @ResponseBodywe can automatically serialize the object returned by the server into a json string through annotations. When passing the json body parameter, we can automatically help @RequestBodyus pass the json character passed from the front end by annotating the object parameter. String deserialization into java object

These functions are HttpMessageConverterrealized through this message conversion tool class

SpringMVC automatically configures Jacksonand GsonHttpMessageConverter, and SpringBoot does automatic configuration for this

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

	}

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

	}

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);
			return converter;
		}

	}

Customize SprinBoot's JSON parsing

Date format parsing

By default, the timestamp type format is returned, but the timestamp will be one day less, and the time zone needs to be added to the database connection url, such as:

spring.datasource.url=jdbc:p6spy:mysql://47.100.78.146:3306/mall?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&autoReconnect=true

  1. @JsonFormatCustom formatting using annotations
	@JsonFormat(pattern = "yyyy-MM-dd")
    private Date birthday;

But this annotation is not flexible enough to add this annotation to the date field in each entity class

  1. Add globally
    Add directly in the configuration filespring.jackson.date-format=yyyy-MM-dd

NULL fields are not returned

  1. If you do not need to return a null field in the interface, you can use @JsonIncludeannotations
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String title;

But this kind of annotation is not flexible enough to add this annotation to the fields in each entity class

  1. Global additions are added directly in the configuration filespring.jackson.default-property-inclusion=non_null

Serialization of custom fields

Custom null 字符串type field returns null character NullStringJsonSerializerserialization

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

Custom null 数字type field returns 0 default value NullIntegerJsonSerializerserialization

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

Custom type 4 is rounded to 5 and serialized 浮点小数with 2 decimal placesDoubleJsonSerialize

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

    }
}

custom NullArrayJsonSerializerserialization

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

Custom BeanSerializerModifieruse 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) {
    
     // 循环所有的beanPropertyWriter
        for (int i = 0; i < beanProperties.size(); i++) {
    
    
            BeanPropertyWriter writer = (BeanPropertyWriter) beanProperties.get(i);
            // 判断字段的类型,如果是array,list,set则注册nullSerializer
            if (isArrayType(writer)) {
    
     //给writer注册一个自己的nullSerializer
                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;
    } // 判断是什么类型

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

    protected JsonSerializer defaultNullStringJsonSerializer() {
    
    
        return _nullStringJsonSerializer;
    }

    protected JsonSerializer defaultNullIntegerJsonSerializer() {
    
    
        return _nullIntegerJsonSerializer;
    }

    protected JsonSerializer defaultDoubleJsonSerializer() {
    
    
        return _doubleJsonSerializer;
    }
}

Apply our own bean serialization to make it effective Provide MappingJackson2HttpMessageConverter class
Provide MappingJackson2HttpMessageConverter class in configuration class, use ObjectMapper for global serialization

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

        ObjectMapper mapper = converter.getObjectMapper();

        // 为mapper注册一个带有SerializerModifier的Factory,此modifier主要做的事情为:判断序列化类型,根据类型指定为null时的值

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

        return converter;
    }
}

This class will replace SpringBoot's default json parsing scheme. In fact, what works in this class is ObjectMapperthe class, so this class can also be configured directly.

 @Bean
    public ObjectMapper om() {
    
    
        ObjectMapper mapper = new ObjectMapper();
        // 为mapper注册一个带有SerializerModifier的Factory,此modifier主要做的事情为:判断序列化类型,根据类型指定为null时的值

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

Customize serialization through the above method, and you can also @JsonSerializecustomize serialization through annotations such as:

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

Then you need to use the field to add

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

Configuration file jackson detailed configuration

  spring:
    jackson:
      # 设置属性命名策略,对应jackson下PropertyNamingStrategy中的常量值,SNAKE_CASE-返回的json驼峰式转下划线,json body下划线传到后端自动转驼峰式
      property-naming-strategy: SNAKE_CASE
      # 全局设置@JsonFormat的格式pattern
      date-format: yyyy-MM-dd HH:mm:ss
      # 当地时区
      locale: zh
      # 设置全局时区
      time-zone: GMT+8
      # 常用,全局设置pojo或被@JsonInclude注解的属性的序列化方式
      default-property-inclusion: NON_NULL #不为空的属性才会序列化,具体属性可看JsonInclude.Include
      # 常规默认,枚举类SerializationFeature中的枚举属性为key,值为boolean设置jackson序列化特性,具体key请看SerializationFeature源码
      serialization:
        WRITE_DATES_AS_TIMESTAMPS: true # 返回的java.util.date转换成timestamp
        FAIL_ON_EMPTY_BEANS: true # 对象为空时是否报错,默认true
      # 枚举类DeserializationFeature中的枚举属性为key,值为boolean设置jackson反序列化特性,具体key请看DeserializationFeature源码
      deserialization:
        # 常用,json中含pojo不存在属性时是否失败报错,默认true
        FAIL_ON_UNKNOWN_PROPERTIES: false
      # 枚举类MapperFeature中的枚举属性为key,值为boolean设置jackson ObjectMapper特性
      # ObjectMapper在jackson中负责json的读写、json与pojo的互转、json tree的互转,具体特性请看MapperFeature,常规默认即可
      mapper:
        # 使用getter取代setter探测属性,如类中含getName()但不包含name属性与setName(),传输的vo json格式模板中依旧含name属性
        USE_GETTERS_AS_SETTERS: true #默认false
      # 枚举类JsonParser.Feature枚举类中的枚举属性为key,值为boolean设置jackson JsonParser特性
      # JsonParser在jackson中负责json内容的读取,具体特性请看JsonParser.Feature,一般无需设置默认即可
      parser:
        ALLOW_SINGLE_QUOTES: true # 是否允许出现单引号,默认false
      # 枚举类JsonGenerator.Feature枚举类中的枚举属性为key,值为boolean设置jackson JsonGenerator特性,一般无需设置默认即可
      # JsonGenerator在jackson中负责编写json内容,具体特性请看JsonGenerator.Feature
 

Guess you like

Origin blog.csdn.net/u011738045/article/details/119392207
Recommended