Spring Boot支持与三种JSON mapping库集成:Gson、Jackson和JSON-B。Jackson是首选和默认的。
Jackson是spring-boot-starter-json的一部分,spring-boot-starter-web中包含spring-boot-starter-json。也就是说,当项目中引入spring-boot-starter-web后会自动引入spring-boot-starter-json。
org.springframework.boot
spring-boot-starter-web
ObjectMapper是jackson-databind包中的一个类,提供读写JSON的功能,可以方便的进行对象和JSON转换:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public final class JsonUtil {
private static ObjectMapper mapper = new ObjectMapper();
private JsonUtil() {
}
/**
* Serialize any Java value as a String.
*/
public static String generate(Object object) throws JsonProcessingException {
return mapper.writeValueAsString(object);
}
/**
* Deserialize JSON content from given JSON content String.
*/
public static T parse(String content, Class valueType) throws IOException {
return mapper.readValue(content, valueType);
}
}
编写一简单POJO测试类:
import java.util.Date;
public class Hero {
public static void main(String[] args) throws Exception {
System.out.println(JsonUtil.generate(new Hero("Jason", new Date())));
}
private String name;
private Date birthday;
public Hero() {
}
public Hero(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
public String getName() {
return name;
}
public Date getBirthday() {
return birthday;
}
}
运行后输出结果如下:
{"name":"Jason","birthday":1540909420353}
可以看到日期转换为长整型。
ObjectMapper默认序列化配置启用了SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,日期将转换为Timestamp。可查看如下源码:
public ObjectMapper(JsonFactory jf, DefaultSerializerProvider sp, DefaultDeserializationContext dc) {
...
BaseSettings base = DEFAULT_BASE.withClassIntrospector(defaultClassIntrospector());
_configOverrides = new ConfigOverrides();
_serializationConfig = new SerializationConfig(base, _subtypeResolver, mixins, rootNames, _configOverrides);
...
}
public SerializationConfig(BaseSettings base, SubtypeResolver str, SimpleMixInResolver mixins, RootNameLookup rootNames,
ConfigOverrides configOverrides)
{
super(base, str, mixins, rootNames, configOverrides);
_serFeatures = collectFeatureDefaults(SerializationFeature.class);
_filterProvider = null;
_defaultPrettyPrinter = DEFAULT_PRETTY_PRINTER;
_generatorFeatures = 0;
_generatorFeaturesToChange = 0;
_formatWriteFeatures = 0;
_formatWriteFeaturesToChange = 0;
}
默认情况下,Date类型序列化将调用DateSerializer的_timestamp 方法:
/**
* For efficiency, we will serialize Dates as longs, instead of
* potentially more readable Strings.
*/
@JacksonStdImpl
@SuppressWarnings("serial")
public class DateSerializer extends DateTimeSerializerBase {
...
@Override
protected long _timestamp(Date value) {
return (value == null) ? 0L : value.getTime();
}
@Override
public void serialize(Date value, JsonGenerator g, SerializerProvider provider) throws IOException {
if (_asTimestamp(provider)) {
g.writeNumber(_timestamp(value));
return;
}
_serializeAsString(value, g, provider);
}
}
DateTimeSerializerBase的_asTimestamp方法:
protected boolean _asTimestamp(SerializerProvider serializers)
{
if (_useTimestamp != null) {
return _useTimestamp.booleanValue();
}
if (_customFormat == null) {
if (serializers != null) {
return serializers.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
// 12-Jun-2014, tatu: Is it legal not to have provider? Was NPE:ing earlier so leave a check
throw new IllegalArgumentException("Null SerializerProvider passed for "+handledType().getName());
}
return false;
}
若要将日期序列化为字符串,可禁用SerializationFeature.WRITE_DATES_AS_TIMESTAMPS:
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
这时序列化将调用StdDateFormat的format()方法,使用ISO-8601兼容格式"yyyy-MM-dd'T'HH:mm:ss.SSSZ",输出内容如下:
{"name":"Jason","birthday":"2018-10-31T03:07:34.485+0000"}
StdDateFormat反序列化支持ISO-8601兼容格式和RFC-1123("EEE, dd MMM yyyy HH:mm:ss zzz")格式。
使用@JsonFormat注解,代替全局设置,是一种更灵活的方法:
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Date birthday;
还可以定义pattern:
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date birthday;
当自定义pattern后,将创建新的SimpleDateFormat实例来序列化日期,参见DateTimeSerializerBase的createContextual()方法:
public JsonSerializer> createContextual(SerializerProvider serializers, BeanProperty property) throws JsonMappingException
{
...
if (format.hasPattern()) {
final Locale loc = format.hasLocale() ? format.getLocale() : serializers.getLocale();
SimpleDateFormat df = new SimpleDateFormat(format.getPattern(), loc);
TimeZone tz = format.hasTimeZone() ? format.getTimeZone() : serializers.getTimeZone();
df.setTimeZone(tz);
return withFormat(Boolean.FALSE, df);
}
...
}
Spring Boot使用HttpMessageConverters处理HTTP交换中的内容转换。当classpath中存在Jackson时,Jackson2ObjectMapperBuilder将是默认的Converter,源码请查看HttpMessageConverters和WebMvcConfigurationSupport:
HttpMessageConverters
private List> getDefaultConverters() {
List> converters = new ArrayList<>();
if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport", null)) {
converters.addAll(new WebMvcConfigurationSupport() {
public List> defaultMessageConverters() {
return super.getMessageConverters();
}
}.defaultMessageConverters());
}
else {
converters.addAll(new RestTemplate().getMessageConverters());
}
reorderXmlConvertersToEnd(converters);
return converters;
}
WebMvcConfigurationSupport
protected final void addDefaultHttpMessageConverters(List> messageConverters) {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new ResourceRegionHttpMessageConverter());
messageConverters.add(new SourceHttpMessageConverter<>());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
...
if (jackson2Present) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
...
}
默认,Jackson2ObjectMapperBuilder将创建ObjectMapper实例:
Jackson2ObjectMapperBuilder
public T build() {
ObjectMapper mapper;
if (this.createXmlMapper) {
mapper = (this.defaultUseWrapper != null ?
new XmlObjectMapperInitializer().create(this.defaultUseWrapper) :
new XmlObjectMapperInitializer().create());
}
else {
mapper = (this.factory != null ? new ObjectMapper(this.factory) : new ObjectMapper());
}
configure(mapper);
return (T) mapper;
}
ObjectMapper以下属性被禁用:
Jackson2ObjectMapperBuilder
private void customizeDefaultFeatures(ObjectMapper objectMapper) {
if (!this.features.containsKey(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
configureFeature(objectMapper, MapperFeature.DEFAULT_VIEW_INCLUSION, false);
}
if (!this.features.containsKey(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
configureFeature(objectMapper, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
}
JacksonAutoConfiguration
static {
Map
针对ObjectMapper的六种Feature,Spring Boot都提供了相应的配置,列表如下:
Feature(Enum) | Spring Boot Property | Values |
---|---|---|
com.fasterxml.jackson.databind.DeserializationFeature | spring.jackson.deserialization.feature_name | true, false |
com.fasterxml.jackson.core.JsonGenerator.Feature | spring.jackson.generator.feature_name | true, false |
com.fasterxml.jackson.databind.MapperFeature | spring.jackson.mapper.feature_name | true, false |
com.fasterxml.jackson.core.JsonParser.Feature | spring.jackson.parser.feature_name | true, false |
com.fasterxml.jackson.databind.SerializationFeature | spring.jackson.serialization.feature_name | true, false |
com.fasterxml.jackson.annotation.JsonInclude.Include | spring.jackson.default-property-inclusion | always, non_null, non_absent, non_default, non_empty |
例如,为启用美化打印,设置spring.jackson.serialization.indent_output=true,相当于启用SerializationFeature.INDENT_OUTPUT,配置中忽略feature_name大小写。
其他的Jackson配置属性:
yyyy-MM-dd HH:mm:ss
.其中spring.jackson.date-format默认值为com.fasterxml.jackson.databind.util.StdDateFormat。
在REST编程中,当提交application/json的POST/PUT请求时,JSON会通过Jackson进行转换。当提交GET请求时,如参数中包含日期,后台代码需要使用注解@DateTimeFormat:
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date startDate;
两者可以同时使用:
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date startDate;