From 201fb5e534a7fa7c32460cadf63d254496c2e687 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 12 Feb 2015 15:53:53 +0000 Subject: [PATCH] Make Joda DateTime serialization format configurable We allow the serialization format of dates to be configured using spring.jackson.date-format. However, this property only applies to java.util.Date instances and has no effect on a Joda DateTime. This commit updates our auto-configuration for Jackson to allow the format string that is used to serialize a Joda DateTime to be configured. A new property, spring.jackson.joda-date-time-format has been introduced. When configured, it is used to configure the serialization format for a Joda DateTime. When it is not configured, we fall back to using spring.jackson.date-format. If this fails, either because the format string is incompatible (unlikely) or because the user's configured the fully-qualified name of a DateFormat class, a warning is logged encouraging the use of spring.jackson.joda-date-time-format. Fixes gh-2225 --- .../jackson/JacksonAutoConfiguration.java | 51 +++++++++++++++++++ .../jackson/JacksonProperties.java | 15 ++++++ .../JacksonAutoConfigurationTests.java | 20 ++++++++ 3 files changed, 86 insertions(+) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java index 94beabf9fc1..f4e4f9e456b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java @@ -25,6 +25,10 @@ import java.util.Map.Entry; import javax.annotation.PostConstruct; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.ListableBeanFactory; @@ -47,6 +51,9 @@ 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.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.joda.ser.DateTimeSerializer; +import com.fasterxml.jackson.datatype.joda.ser.JacksonJodaFormat; /** * Auto configuration for Jackson. The following auto-configuration will get applied: @@ -97,6 +104,50 @@ public class JacksonAutoConfiguration { } + @Configuration + @ConditionalOnClass({ Jackson2ObjectMapperBuilder.class, DateTime.class, + DateTimeSerializer.class }) + static class JodaDateTimeJacksonConfiguration { + + private final Log log = LogFactory.getLog(JodaDateTimeJacksonConfiguration.class); + + @Autowired + private JacksonProperties jacksonProperties; + + @Bean + public Module jodaDateTimeSerializationModule() { + SimpleModule module = new SimpleModule(); + + JacksonJodaFormat jacksonJodaFormat = null; + + if (this.jacksonProperties.getJodaDateTimeFormat() != null) { + jacksonJodaFormat = new JacksonJodaFormat(DateTimeFormat.forPattern( + this.jacksonProperties.getJodaDateTimeFormat()).withZoneUTC()); + } + else if (this.jacksonProperties.getDateFormat() != null) { + try { + jacksonJodaFormat = new JacksonJodaFormat(DateTimeFormat.forPattern( + this.jacksonProperties.getDateFormat()).withZoneUTC()); + } + catch (IllegalArgumentException ex) { + if (this.log.isWarnEnabled()) { + this.log.warn("spring.jackson.date-format could not be used to " + + "configure formatting of Joda's DateTime. You may want " + + "to configure spring.jackson.joda-date-time-format as " + + "well."); + } + } + } + + if (jacksonJodaFormat != null) { + module.addSerializer(DateTime.class, new DateTimeSerializer( + jacksonJodaFormat)); + } + + return module; + } + } + @Configuration @ConditionalOnClass({ ObjectMapper.class, Jackson2ObjectMapperBuilder.class }) @EnableConfigurationProperties({ HttpMapperProperties.class, JacksonProperties.class }) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java index 07317ea1876..dbdaa1b4937 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java @@ -43,6 +43,13 @@ public class JacksonProperties { */ private String dateFormat; + /** + * Joda date time format string (yyyy-MM-dd HH:mm:ss). If not configured, + * {@code date-format} will be used as a fallback if it is configured with a format + * string. + */ + private String jodaDateTimeFormat; + /** * One of the constants on Jackson's PropertyNamingStrategy * (CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES). Can also be a fully-qualified class @@ -83,6 +90,14 @@ public class JacksonProperties { this.dateFormat = dateFormat; } + public String getJodaDateTimeFormat() { + return this.jodaDateTimeFormat; + } + + public void setJodaDateTimeFormat(String jodaDataTimeFormat) { + this.jodaDateTimeFormat = jodaDataTimeFormat; + } + public String getPropertyNamingStrategy() { return this.propertyNamingStrategy; } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java index 6c94593adc1..65da35b082b 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java @@ -23,6 +23,7 @@ import java.util.HashSet; import java.util.Set; import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; import org.joda.time.LocalDateTime; import org.junit.After; import org.junit.Before; @@ -134,6 +135,23 @@ public class JacksonAutoConfigurationTests { "spring.jackson.date-format:yyyyMMddHHmmss"); this.context.refresh(); ObjectMapper mapper = this.context.getBean(ObjectMapper.class); + DateTime dateTime = new DateTime(1988, 6, 25, 20, 30, DateTimeZone.UTC); + assertEquals("\"19880625203000\"", mapper.writeValueAsString(dateTime)); + dateTime = new DateTime(1988, 6, 25, 20, 30); + Date date = dateTime.toDate(); + assertEquals("\"19880625203000\"", mapper.writeValueAsString(date)); + } + + @Test + public void customJodaDateTimeFormat() throws Exception { + this.context.register(JacksonAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.jackson.date-format:yyyyMMddHHmmss", + "spring.jackson.joda-date-time-format:yyyy-MM-dd HH:mm:ss"); + this.context.refresh(); + ObjectMapper mapper = this.context.getBean(ObjectMapper.class); + DateTime dateTime = new DateTime(1988, 6, 25, 20, 30, DateTimeZone.UTC); + assertEquals("\"1988-06-25 20:30:00\"", mapper.writeValueAsString(dateTime)); Date date = new DateTime(1988, 6, 25, 20, 30).toDate(); assertEquals("\"19880625203000\"", mapper.writeValueAsString(date)); } @@ -147,6 +165,8 @@ public class JacksonAutoConfigurationTests { "spring.jackson.date-format:org.springframework.boot.autoconfigure.jackson.JacksonAutoConfigurationTests.MyDateFormat"); this.context.refresh(); ObjectMapper mapper = this.context.getBean(ObjectMapper.class); + DateTime dateTime = new DateTime(1988, 6, 25, 20, 30, DateTimeZone.UTC); + assertEquals("\"1988-06-25T20:30:00.000Z\"", mapper.writeValueAsString(dateTime)); Date date = new DateTime(1988, 6, 25, 20, 30).toDate(); assertEquals("\"1988-06-25 20:30:00\"", mapper.writeValueAsString(date)); }