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。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
ObjectMapper
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> T parse(String content, Class<T> 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<Date> {
...
@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;
}
禁用WRITE_DATES_AS_TIMESTAMPS
若要將日期序列化爲字符串,可禁用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註解,代替全局設置,是一種更靈活的方法:
@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與Jackson ObjectMapper
Spring Boot使用HttpMessageConverters處理HTTP交換中的內容轉換。當classpath中存在Jackson時,Jackson2ObjectMapperBuilder將是默認的Converter,源碼請查看HttpMessageConverters和WebMvcConfigurationSupport:
HttpMessageConverters
private List<HttpMessageConverter<?>> getDefaultConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport", null)) {
converters.addAll(new WebMvcConfigurationSupport() {
public List<HttpMessageConverter<?>> defaultMessageConverters() {
return super.getMessageConverters();
}
}.defaultMessageConverters());
}
else {
converters.addAll(new RestTemplate().getMessageConverters());
}
reorderXmlConvertersToEnd(converters);
return converters;
}
WebMvcConfigurationSupport
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> 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 extends ObjectMapper> 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以下屬性被禁用:
- MapperFeature.DEFAULT_VIEW_INCLUSION
- DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
- SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
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<Object, Boolean> featureDefaults = new HashMap<>();
featureDefaults.put(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
FEATURE_DEFAULTS = Collections.unmodifiableMap(featureDefaults);
}
自定義Jackson ObjectMapper配置
針對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配置屬性:
- spring.jackson.date-format= # Date format string or a fully-qualified date format class name. For instance,
yyyy-MM-dd HH:mm:ss
. - spring.jackson.joda-date-time-format= # Joda date time format string. If not configured, "date-format" is used as a fallback if it is configured with a format string.
- spring.jackson.locale= # Locale used for formatting.
- spring.jackson.property-naming-strategy= # One of the constants on Jackson's PropertyNamingStrategy. Can also be a fully-qualified class name of a PropertyNamingStrategy subclass.
- spring.jackson.time-zone= # Time zone used when formatting dates. For instance, "America/Los_Angeles" or "GMT+10".
其中spring.jackson.date-format默認值爲com.fasterxml.jackson.databind.util.StdDateFormat。
@DateTimeFormat和@JsonFormat
在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;