Spring Boot支持與三種JSON mapping庫集成:Gson、Jackson和JSON-B。Jackson是首選和默認的。html
Jackson是spring-boot-starter-json依賴中的一部分,spring-boot-starter-web中包含spring-boot-starter-json。也就是說,當項目中引入spring-boot-starter-web後會自動引入spring-boot-starter-json。java
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
ObjectMapper是jackson-databind包中的一個類,提供讀寫JSON的功能,能夠方便的進行對象和JSON轉換:web
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測試類:spring
import java.util.Date; public class Hero { private String name; private Date birthday; public static void main(String[] args) throws Exception { System.out.println(JsonUtil.generate(new Hero("Jason", new Date()))); } 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}
上例,默認日期轉換爲長整型。json
ObjectMapper默認序列化配置啓用了SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,日期將轉換爲Timestamp。可查看以下源碼:api
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 方法:app
/** * 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方法:ide
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:spring-boot
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); } ... }
不過多解釋各註解的做用,請看輸出結果。
示例1
import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyOrder; @JsonPropertyOrder({"firstName", "lastName"}) @JsonIgnoreProperties({"id"}) @JsonInclude(JsonInclude.Include.NON_NULL) public class Hero { private Integer id; // define one or more alternative names for a property during deserialization. @JsonAlias({"fName", "f_name"}) private String firstName; private String lastName; public static void main(String[] args) throws Exception { System.out.println(JsonUtil.generate(new Hero(1, "Jason", "Sun"))); System.out.println(JsonUtil.generate(new Hero(1, "Jason", null))); System.out.println(JsonUtil.parse("{\"fName\":\"Jason\",\"lastName\":\"Sun\"}", Hero.class).getFirstName()); } public Hero() { } public Hero(Integer id, String firstName, String lastName) { this.id = id; this.firstName = firstName; this.lastName = lastName; } // getter and setter }
輸出結果:
{"firstName":"Jason","lastName":"Sun"} {"firstName":"Jason"} Jason
示例2
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreType; import com.fasterxml.jackson.annotation.JsonProperty; public class Hero { @JsonIgnore private Integer id; private String nickname; private Name name; @JsonProperty("mail") private String email; @JsonIgnoreType public static class Name { private String firstName; private String lastName; public Name(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } // getter and setter } public static void main(String[] args) throws Exception { System.out.println(JsonUtil.generate(new Hero(1, "chuanchuan", new Name("Jason", "Sun"), "jason@163.com"))); } public Hero() { } public Hero(Integer id, String nickname, Name name, String email) { this.id = id; this.nickname = nickname; this.name = name; this.email = email; } // getter and setter }
輸出結果:
{"nickname":"chuanchuan","mail":"jason@163.com"}
示例3
使用@JsonValue控制整個類序列化的結果,一個類中最多隻能含有一個@JsonValue。
import com.fasterxml.jackson.annotation.JsonValue; public class HeroWithValue { private Integer id; @JsonValue private String name; private String email; public static void main(String[] args) throws Exception { System.out.println(JsonUtil.generate(new HeroWithValue(1, "Jason", "jason@163.com"))); } public HeroWithValue(Integer id, String name, String email) { this.id = id; this.name = name; this.email = email; } // getter and setter }
輸出結果:
"Jason"
示例4
public class Views { public static class Public { } public static class Internal extends Public { } }
import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.ObjectMapper; public class HeroWithView { @JsonView(Views.Public.class) private int id; @JsonView(Views.Public.class) private String name; @JsonView(Views.Internal.class) private String email; public static void main(String[] args) throws Exception { HeroWithView hero = new HeroWithView(1, "Jason", "jason@163.com"); String publicResult = new ObjectMapper().writerWithView(Views.Public.class).writeValueAsString(hero); String internalResult = new ObjectMapper().writerWithView(Views.Internal.class).writeValueAsString(hero); System.out.println(publicResult); System.out.println(internalResult); } public HeroWithView(int id, String name, String email) { this.id = id; this.name = name; this.email = email; } }
輸出結果:
{"id":1,"name":"Jason"} {"id":1,"name":"Jason","email":"jason@163.com"}
示例5
import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @JsonRootName(value = "hero") public class UnwrappedHero { private int id; @JsonUnwrapped private Name name; public static void main(String[] args) throws Exception { ObjectMapper mapper = new ObjectMapper(); mapper.enable(SerializationFeature.WRAP_ROOT_VALUE); System.out.println(mapper.writeValueAsString(new UnwrappedHero(1, new Name("Jason", "Sun")))); } public UnwrappedHero(int id, Name name) { this.id = id; this.name = name; } public static class Name { private String firstName; private String lastName; public Name(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } // getter and setter } // getter and setter }
示例6
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; public class Zoo { private Animal animal; public Zoo(Animal animal) { this.animal = animal; } @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = Dog.class, name = "dog"), @JsonSubTypes.Type(value = Cat.class, name = "cat") }) public static class Animal { private String name; public Animal(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } @JsonTypeName("dog") public static class Dog extends Animal { private double barkVolume; public Dog(String name) { super(name); } public double getBarkVolume() { return barkVolume; } public void setBarkVolume(double barkVolume) { this.barkVolume = barkVolume; } } @JsonTypeName("cat") public static class Cat extends Animal { private boolean likesCream; private int lives; public Cat(String name) { super(name); } public boolean isLikesCream() { return likesCream; } public void setLikesCream(boolean likesCream) { this.likesCream = likesCream; } public int getLives() { return lives; } public void setLives(int lives) { this.lives = lives; } } public static void main(String[] args) throws Exception { System.out.println(JsonUtil.generate(new Zoo(new Dog("lacy")))); System.out.println(JsonUtil.generate(new Zoo(new Cat("tom")))); } public Animal getAnimal() { return animal; } public void setAnimal(Animal animal) { this.animal = animal; } }
輸出結果:
{"animal":{"type":"dog","name":"lacy","barkVolume":0.0}} {"animal":{"type":"cat","name":"tom","likesCream":false,"lives":0}}
示例7
雙向關聯時如未作處理,會發生錯誤:com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError),可組合使用@JsonManagedReference和@JsonBackReference。
public class Hero { private Integer id; private String name; @JsonManagedReference private List<Race> races = new ArrayList<>(); public static void main(String[] args) throws Exception { Hero hero = new Hero(1, "jason"); Race race = new Race(1, "marathon", 42.195f); hero.addRace(race); System.out.println(JsonUtil.generate(hero)); System.out.println(JsonUtil.generate(race)); } public Hero(Integer id, String name) { this.id = id; this.name = name; } public void addRace(Race race) { races.add(race); race.setHero(this); } // getter and setter }
import com.fasterxml.jackson.annotation.JsonBackReference; public class Race { private Integer id; private String type; private Float distance; @JsonBackReference private Hero hero; public Race(Integer id, String type, Float distance) { this.id = id; this.type = type; this.distance = distance; } // getter and setter }
輸出結果:
{"id":1,"name":"jason","races":[{"id":1,"type":"marathon","distance":42.195}]} {"id":1,"type":"marathon","distance":42.195}
示例8
雙向關聯的另外一種解決方案,使用@JsonIdentityInfo。
import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.ObjectIdGenerators; import java.util.ArrayList; import java.util.List; @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class Hero { private Integer id; private String name; private List<Race> races = new ArrayList<>(); public static void main(String[] args) throws Exception { Hero hero = new Hero(1, "jason"); Race race = new Race(1, "marathon", 42.195f); hero.addRace(race); System.out.println(JsonUtil.generate(hero)); System.out.println(JsonUtil.generate(race)); } public Hero(Integer id, String name) { this.id = id; this.name = name; } public void addRace(Race race) { races.add(race); race.setHero(this); } // getter and setter }
import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.ObjectIdGenerators; @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class Race { private Integer id; private String type; private Float distance; private Hero hero; public Race(Integer id, String type, Float distance) { this.id = id; this.type = type; this.distance = distance; } // getter and setter }
輸出結果:
{"id":1,"name":"jason","races":[{"id":1,"type":"marathon","distance":42.195,"hero":1}]} {"id":1,"type":"marathon","distance":42.195,"hero":{"id":1,"name":"jason","races":[1]}}
示例9
上例,若是要Race序列化結果僅含有hero id,能夠組合使用@JsonIdentityInfo和@JsonIdentityReference。
import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.ObjectIdGenerators; import java.util.ArrayList; import java.util.List; @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", resolver = HeroIdResolver.class) public class Hero { private Integer id; private String name; private List<Race> races = new ArrayList<>(); public static void main(String[] args) throws Exception { Hero hero = new Hero(1, "jason"); Race race = new Race(1, "marathon", 42.195f); hero.addRace(race); System.out.println(JsonUtil.generate(hero)); System.out.println(JsonUtil.generate(race)); } public Hero(Integer id) { this.id = id; } public Hero(Integer id, String name) { this.id = id; this.name = name; } public void addRace(Race race) { races.add(race); race.setHero(this); } // getter and setter }
import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.ObjectIdGenerators; @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class Race { private Integer id; private String type; private Float distance; @JsonIdentityReference(alwaysAsId = true) @JsonProperty("heroId") private Hero hero; public Race(Integer id, String type, Float distance) { this.id = id; this.type = type; this.distance = distance; } // getter and setter }
爲了支持反序列化,須要自定義ObjectIdResolver:
import com.fasterxml.jackson.annotation.ObjectIdGenerator; import com.fasterxml.jackson.annotation.ObjectIdResolver; public class HeroIdResolver implements ObjectIdResolver { @Override public void bindItem(ObjectIdGenerator.IdKey id, Object pojo) { } @Override public Object resolveId(ObjectIdGenerator.IdKey id) { return new Hero((Integer) id.key); } @Override public ObjectIdResolver newForDeserialization(Object context) { return new HeroIdResolver(); } @Override public boolean canUseFor(ObjectIdResolver resolverType) { return resolverType.getClass() == getClass(); } }
輸出結果:
{"id":1,"name":"jason","races":[{"id":1,"type":"marathon","distance":42.195,"heroId":1}]} {"id":1,"type":"marathon","distance":42.195,"heroId":1}
示例10
自定義Annotation,將多個Annotation組合起來。
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) @JacksonAnnotationsInside @JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({"name", "id", "dateCreated"}) public @interface CustomAnnotation { }
import java.util.Date; @CustomAnnotation public class HeroWithCustomAnnotation { private Integer id; private String name; private Date dateCreated; public static void main(String[] args) throws Exception { System.out.println(JsonUtil.generate(new HeroWithCustomAnnotation(1, "Jason", null))); } public HeroWithCustomAnnotation(Integer id, String name, Date dateCreated) { this.id = id; this.name = name; this.dateCreated = dateCreated; } // getter and setter }
輸出結果:
{"name":"Jason","id":1}
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; } ... 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); } }
默認禁用了MapperFeature.DEFAULT_VIEW_INCLUSION、DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES。
Auto Configurationk中默認禁用了WRITE_DATES_AS_TIMESTAMPS:
JacksonAutoConfiguration
static { Map<Object, Boolean> featureDefaults = new HashMap<>(); featureDefaults.put(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); FEATURE_DEFAULTS = Collections.unmodifiableMap(featureDefaults); }
針對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;
@RestController @RequestMapping(value = "/api", produces = MediaType.APPLICATION_JSON_VALUE) @Api(tags = {"Hello Controller"}) public class HelloController { ... }
JSON MediaType爲"application/json;charset=UTF-8",默認charset爲"UTF-8"。
若是遺留代碼使用了GBK編碼,咱們修改produces爲"application/json;charset=GBK",會生效麼?根據JSON規範The JavaScript Object Notation (JSON) Data Interchange Format - Character Encoding,JSON僅支持UTF-八、UTF-1六、UTF-32編碼。
查看源碼驗證一下:
AbstractJackson2HttpMessageConverter
public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> { @Override protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { MediaType contentType = outputMessage.getHeaders().getContentType(); JsonEncoding encoding = getJsonEncoding(contentType); JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding); ... } ... protected JsonEncoding getJsonEncoding(@Nullable MediaType contentType) { if (contentType != null && contentType.getCharset() != null) { Charset charset = contentType.getCharset(); for (JsonEncoding encoding : JsonEncoding.values()) { if (charset.name().equals(encoding.getJavaName())) { return encoding; } } } return JsonEncoding.UTF8; } }
JsonEncoding
public enum JsonEncoding { UTF8("UTF-8", false, 8), // N/A for big-endian, really UTF16_BE("UTF-16BE", true, 16), UTF16_LE("UTF-16LE", false, 16), UTF32_BE("UTF-32BE", true, 32), UTF32_LE("UTF-32LE", false, 32) ... }
能夠看到JsonEncoding僅包含UTF-八、UTF-16BE、UTF-16LE、UTF-32BE、UTF-32LE幾種編碼。