此时我们的返回结构如下:
{ "code": 200, "msg": "ok", "data": { "id": 1, "username": "steve", "secretKey": "xxx", "expiredAt": null, "createdAt": "2020-07-07T06:09:15" } } 复制代码
但上面有几个问题:
• 我希望字段是以下划线命名方式,也就是 createdAt 改成 created_at 这样
• 我希望某些字段值的输出格式可以自定义,比如日期类型我希望输出是 yyyy-MM-dd HH:mm:ss
• 我不希望 secretKey 这类具有安全性质的字段返回给调用方
• 我不希望有 null 这样的输出,避免给调用方不必要的麻烦
我们有两种选择,第一种是在每一个字段上通过添加 @JsonProperty 注解来实现,如下:
@JsonProperty("secret_key") private String secretKey; 复制代码
这种方式灵活度高,缺点就是繁琐,变量名是单个单词的不用转换,多个单词的如果要保持统一格式就需要每个都写上,工作量不小。
第二种方式就是全局配置 Spring 内置的 Jackson 的序列化转换器,在 config 目录下新建 JsonConfig.java 文件:
package com.foxescap.wxbox.config; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.time.format.DateTimeFormatter; import java.util.List; /** * @author xfly */ @EnableWebMvc @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Bean public LocalDateTimeSerializer localDateTimeSerializer() { return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add( new MappingJackson2HttpMessageConverter( new Jackson2ObjectMapperBuilder() .propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE) .build() ) ); } } 复制代码
我们通过重写 WebMvcConfigurer 接口的 configureMessageConverters 方法,添加自定义的 JSON 转换器,关键是 propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE) 这行代码,设置属性的命名策略为下划线命名方式。
最常见的就是对时间类型的字段格式化,也有两种方式,第一种是在每个字段上添加 @JsonFormat 注解,比如格式化日期时间:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime expiredAt; 复制代码
也可以全局配置,我们在上面 JsonConfig 代码的基础上,加上一个类型串行器:
@Bean public LocalDateTimeSerializer localDateTimeSerializer() { return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add( new MappingJackson2HttpMessageConverter( new Jackson2ObjectMapperBuilder() .propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE) .serializerByType(LocalDateTime.class, localDateTimeSerializer()) .build() ) ); } 复制代码
这样就能对全局 LocalDateTime 类型的字段序列化时转换成我们自定义的格式了。
当我们不需要有字段被序列化,即需要忽略它,那么可以在那个字段上添加 @JsonIgnore 注解即可。
一般地,要么是直接忽略值为 null 的字段,要么是将 null 转换成空字符串处理,前者可以直接在每个需要的字段上加 @JsonInclude(Include.NON_NULL) 注解,或者也可以在每个需要序列化的类上加,当然也可以全局配置,在 .build() 前加入 .serializationInclusion(JsonInclude.Include.NON_NULL) 即可。
如果我们不希望 null 值直接被忽略,又不需要直接给调用方返回 null,那么可以添加一个 setNullValueSerializer 方法自定义输出:
@Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { var builder = new Jackson2ObjectMapperBuilder() .propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE) //.serializationInclusion(JsonInclude.Include.NON_NULL) .serializerByType(LocalDateTime.class, localDateTimeSerializer()) .build(); builder.getSerializerProvider() .setNullValueSerializer(new JsonSerializer<>() { @Override public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeString(""); } }); converters.add(new MappingJackson2HttpMessageConverter(builder)); } 复制代码
纠结过是直接不序列化 Null 值还是设为空值,考虑到对于调用方,如果直接将 Null 值忽略了的话,数据的结构完整性就大大破坏了,比如一个数组,有几个数组元素里的字段有,有几个没有,对于调用方就非常不友好了。
如果你想对不同变量类型的 Null 值分别处理的话,那么就需要重写 changeProperties 方法,比如对于数组集合类型的字段,如果是 Null 值则序列化成 [] ;如果是字符串类型的字段,序列化成 "" ;如果是不二类型的字段,序列化成 false 等等:
@Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { var builder = new Jackson2ObjectMapperBuilder() .propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE) .serializerByType(LocalDateTime.class, localDateTimeSerializer()) .build(); builder.setSerializerFactory(builder.getSerializerFactory().withSerializerModifier(new BeanSerializerModifier() { @Override public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) { for (var beanPropertyWriter : beanProperties) { var javaType = beanPropertyWriter.getType(); if (javaType.isArrayType() || javaType.isCollectionLikeType()) { beanPropertyWriter.assignNullSerializer(new JsonSerializer<>() { @Override public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeStartArray(); jsonGenerator.writeEndArray(); } }); } else if (javaType.isTypeOrSubTypeOf(String.class)) { beanPropertyWriter.assignNullSerializer(new JsonSerializer<>() { @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeString(""); } }); } else if (javaType.isTypeOrSuperTypeOf(Boolean.class)) { beanPropertyWriter.assignNullSerializer(new JsonSerializer<>() { @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeBoolean(false); } }); } else if (javaType.isMapLikeType()) { beanPropertyWriter.assignNullSerializer(new JsonSerializer<>() { @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeStartObject(); gen.writeEndObject(); } }); } else if (javaType.isTypeOrSuperTypeOf(Integer.class) || javaType.isTypeOrSuperTypeOf(Long.class) || javaType.isTypeOrSuperTypeOf(Double.class) || javaType.isTypeOrSuperTypeOf(Float.class)) { beanPropertyWriter.assignNullSerializer(new JsonSerializer<>() { @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeNumber(0); } }); } else if (javaType.isTypeOrSuperTypeOf(LocalDateTime.class) || javaType.isTypeOrSuperTypeOf(LocalDate.class)) { beanPropertyWriter.assignNullSerializer(new JsonSerializer<>() { @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeString(""); } }); } } return beanProperties; } })); converters.add(new MappingJackson2HttpMessageConverter(builder)); } 复制代码