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