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
- use
@JsonFormat
Annotate 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
- Global add
Add spring.jackson.date-format= YYYY-MM-dd to the configuration file
NULL fields are not returned
- A null field can be used in an interface if it is not necessary to return a null field
@JsonInclude
annotations
@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
- Global add Is added directly to the configuration file
spring.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