Spring Boot中Jackson ObjectMapper应用详解

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

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

禁用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> 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以下属性被禁用:

  • 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 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;

你可能感兴趣的:(java,架构,springboot,java,架构,springboot)