Make Jackson date format and property naming strategy configurable
Allow the environment to be used to configure Jackson's date format and property naming strategy Closes gh-1628
This commit is contained in:
parent
fcd855cd5e
commit
e070d55491
|
@ -16,11 +16,15 @@
|
|||
|
||||
package org.springframework.boot.autoconfigure.jackson;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -33,6 +37,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
|||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
|
@ -40,6 +46,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
|
|||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.joda.JodaModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
|
||||
|
@ -57,6 +64,7 @@ import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
|
|||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Andy Wilkinson
|
||||
* @author Marcel Overdijk
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Configuration
|
||||
|
@ -107,9 +115,65 @@ public class JacksonAutoConfiguration {
|
|||
configureParserFeatures(objectMapper);
|
||||
configureGeneratorFeatures(objectMapper);
|
||||
|
||||
configureDateFormat(objectMapper);
|
||||
configurePropertyNamingStrategy(objectMapper);
|
||||
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
private void configurePropertyNamingStrategy(ObjectMapper objectMapper) {
|
||||
// We support a fully qualified class name extending Jackson's
|
||||
// PropertyNamingStrategy or a string value corresponding to the constant
|
||||
// names in PropertyNamingStrategy which hold default provided implementations
|
||||
String propertyNamingStrategy = this.jacksonProperties
|
||||
.getPropertyNamingStrategy();
|
||||
if (propertyNamingStrategy != null) {
|
||||
try {
|
||||
Class<?> clazz = ClassUtils.forName(propertyNamingStrategy, null);
|
||||
objectMapper
|
||||
.setPropertyNamingStrategy((PropertyNamingStrategy) BeanUtils
|
||||
.instantiateClass(clazz));
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
// Find the field (this way we automatically support new constants
|
||||
// that may be added by Jackson in the future)
|
||||
Field field = ReflectionUtils.findField(PropertyNamingStrategy.class,
|
||||
propertyNamingStrategy, PropertyNamingStrategy.class);
|
||||
if (field != null) {
|
||||
try {
|
||||
objectMapper
|
||||
.setPropertyNamingStrategy((PropertyNamingStrategy) field
|
||||
.get(null));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Constant named '"
|
||||
+ propertyNamingStrategy + "' not found on "
|
||||
+ PropertyNamingStrategy.class.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void configureDateFormat(ObjectMapper objectMapper) {
|
||||
// We support a fully qualified class name extending DateFormat or a date
|
||||
// pattern string value
|
||||
String dateFormat = this.jacksonProperties.getDateFormat();
|
||||
if (dateFormat != null) {
|
||||
try {
|
||||
Class<?> clazz = ClassUtils.forName(dateFormat, null);
|
||||
objectMapper.setDateFormat((DateFormat) BeanUtils
|
||||
.instantiateClass(clazz));
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
objectMapper.setDateFormat(new SimpleDateFormat(dateFormat));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void configureDeserializationFeatures(ObjectMapper objectMapper) {
|
||||
for (Entry<DeserializationFeature, Boolean> entry : this.jacksonProperties
|
||||
.getDeserialization().entrySet()) {
|
||||
|
|
|
@ -31,11 +31,16 @@ import com.fasterxml.jackson.databind.SerializationFeature;
|
|||
* Configuration properties to configure Jackson
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Marcel Overdijk
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.jackson")
|
||||
public class JacksonProperties {
|
||||
|
||||
private String dateFormat;
|
||||
|
||||
private String propertyNamingStrategy;
|
||||
|
||||
private Map<SerializationFeature, Boolean> serialization = new HashMap<SerializationFeature, Boolean>();
|
||||
|
||||
private Map<DeserializationFeature, Boolean> deserialization = new HashMap<DeserializationFeature, Boolean>();
|
||||
|
@ -46,6 +51,22 @@ public class JacksonProperties {
|
|||
|
||||
private Map<JsonGenerator.Feature, Boolean> generator = new HashMap<JsonGenerator.Feature, Boolean>();
|
||||
|
||||
public String getDateFormat() {
|
||||
return this.dateFormat;
|
||||
}
|
||||
|
||||
public void setDateFormat(String dateFormat) {
|
||||
this.dateFormat = dateFormat;
|
||||
}
|
||||
|
||||
public String getPropertyNamingStrategy() {
|
||||
return this.propertyNamingStrategy;
|
||||
}
|
||||
|
||||
public void setPropertyNamingStrategy(String propertyNamingStrategy) {
|
||||
this.propertyNamingStrategy = propertyNamingStrategy;
|
||||
}
|
||||
|
||||
public Map<SerializationFeature, Boolean> getSerialization() {
|
||||
return this.serialization;
|
||||
}
|
||||
|
|
|
@ -17,8 +17,11 @@
|
|||
package org.springframework.boot.autoconfigure.jackson;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
@ -60,6 +63,8 @@ import static org.mockito.Mockito.verify;
|
|||
*
|
||||
* @author Dave Syer
|
||||
* @author Oliver Gierke
|
||||
* @author Andy Wilkinson
|
||||
* @author Marcel Overdijk
|
||||
*/
|
||||
public class JacksonAutoConfigurationTests {
|
||||
|
||||
|
@ -108,6 +113,110 @@ public class JacksonAutoConfigurationTests {
|
|||
assertEquals("{\"foo\":\"bar\"}", mapper.writeValueAsString(new Foo()));
|
||||
}
|
||||
|
||||
/*
|
||||
* ObjectMapper does not contain method to get the date format of the mapper. See
|
||||
* https://github.com/FasterXML/jackson-databind/issues/559 If such a method will be
|
||||
* provided below tests can be simplified.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void noCustomDateFormat() throws Exception {
|
||||
this.context.register(JacksonAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
ObjectMapper mapper = this.context.getBean(ObjectMapper.class);
|
||||
Date date = new DateTime(1988, 6, 25, 20, 30).toDate();
|
||||
assertEquals(String.valueOf(date.getTime()), mapper.writeValueAsString(date));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customDateFormat() throws Exception {
|
||||
this.context.register(JacksonAutoConfiguration.class);
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"spring.jackson.date-format:yyyyMMddHHmmss");
|
||||
this.context.refresh();
|
||||
ObjectMapper mapper = this.context.getBean(ObjectMapper.class);
|
||||
Date date = new DateTime(1988, 6, 25, 20, 30).toDate();
|
||||
assertEquals("\"19880625203000\"", mapper.writeValueAsString(date));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customDateFormatClass() throws Exception {
|
||||
this.context.register(JacksonAutoConfiguration.class);
|
||||
EnvironmentTestUtils
|
||||
.addEnvironment(
|
||||
this.context,
|
||||
"spring.jackson.date-format:org.springframework.boot.autoconfigure.jackson.JacksonAutoConfigurationTests.MyDateFormat");
|
||||
this.context.refresh();
|
||||
ObjectMapper mapper = this.context.getBean(ObjectMapper.class);
|
||||
Date date = new DateTime(1988, 6, 25, 20, 30).toDate();
|
||||
assertEquals("\"1988-06-25 20:30:00\"", mapper.writeValueAsString(date));
|
||||
}
|
||||
|
||||
public static class MyDateFormat extends SimpleDateFormat {
|
||||
|
||||
public MyDateFormat() {
|
||||
super("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ObjectMapper does not contain method to get the property naming strategy of the
|
||||
* mapper. See https://github.com/FasterXML/jackson-databind/issues/559 If such a
|
||||
* method will be provided below tests can be simplified.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void noCustomPropertyNamingStrategy() throws Exception {
|
||||
this.context.register(JacksonAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
ObjectMapper mapper = this.context.getBean(ObjectMapper.class);
|
||||
assertEquals("{\"propertyName\":null}", mapper.writeValueAsString(new Bar()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customPropertyNamingStrategyCamelCaseToLowerCaseWithUnderscores()
|
||||
throws Exception {
|
||||
this.context.register(JacksonAutoConfiguration.class);
|
||||
EnvironmentTestUtils
|
||||
.addEnvironment(this.context,
|
||||
"spring.jackson.property-naming-strategy:CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES");
|
||||
this.context.refresh();
|
||||
ObjectMapper mapper = this.context.getBean(ObjectMapper.class);
|
||||
assertEquals("{\"property_name\":null}", mapper.writeValueAsString(new Bar()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customPropertyNamingStrategyPascalCaseToCamelCase() throws Exception {
|
||||
this.context.register(JacksonAutoConfiguration.class);
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"spring.jackson.property-naming-strategy:PASCAL_CASE_TO_CAMEL_CASE");
|
||||
this.context.refresh();
|
||||
ObjectMapper mapper = this.context.getBean(ObjectMapper.class);
|
||||
assertEquals("{\"PropertyName\":null}", mapper.writeValueAsString(new Bar()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customPropertyNamingStrategyLowerCase() throws Exception {
|
||||
this.context.register(JacksonAutoConfiguration.class);
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"spring.jackson.property-naming-strategy:LOWER_CASE");
|
||||
this.context.refresh();
|
||||
ObjectMapper mapper = this.context.getBean(ObjectMapper.class);
|
||||
assertEquals("{\"propertyname\":null}", mapper.writeValueAsString(new Bar()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customPropertyNamingStrategyClass() throws Exception {
|
||||
this.context.register(JacksonAutoConfiguration.class);
|
||||
EnvironmentTestUtils
|
||||
.addEnvironment(
|
||||
this.context,
|
||||
"spring.jackson.property-naming-strategy:com.fasterxml.jackson.databind.PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy");
|
||||
this.context.refresh();
|
||||
ObjectMapper mapper = this.context.getBean(ObjectMapper.class);
|
||||
assertEquals("{\"property_name\":null}", mapper.writeValueAsString(new Bar()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableSerializationFeature() throws Exception {
|
||||
this.context.register(JacksonAutoConfiguration.class);
|
||||
|
@ -297,4 +406,16 @@ public class JacksonAutoConfigurationTests {
|
|||
|
||||
}
|
||||
|
||||
protected static class Bar {
|
||||
|
||||
private String propertyName;
|
||||
|
||||
public String getPropertyName() {
|
||||
return this.propertyName;
|
||||
}
|
||||
|
||||
public void setPropertyName(String propertyName) {
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,6 +94,8 @@ content into your application; rather pick only the properties that you need.
|
|||
spring.resources.add-mappings=true # if default mappings should be added
|
||||
|
||||
# JACKSON ({sc-spring-boot-autoconfigure}}/jackson/JacksonProperties.{sc-ext}[JacksonProperties])
|
||||
spring.jackson.date-format= # Date format string (e.g. yyyy-MM-dd HH:mm:ss), or a fully-qualified date format class name (e.g. com.fasterxml.jackson.databind.util.ISO8601DateFormat)
|
||||
spring.jackson.property-naming-strategy= # One of the constants on Jackson's PropertyNamingStrategy (e.g. CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES) or the fully-qualified class name of a PropertyNamingStrategy subclass
|
||||
spring.jackson.deserialization.*= # see Jackson's DeserializationFeature
|
||||
spring.jackson.generator.*= # see Jackson's JsonGenerator.Feature
|
||||
spring.jackson.mapper.*= # see Jackson's MapperFeature
|
||||
|
|
Loading…
Reference in New Issue