39

According to http://wiki.fasterxml.com/JacksonFAQDateHandling, “DateTime can be automatically serialized/deserialized similar to how java.util.Date is handled.” However, I am not able to accomplish this automatic functionality. There are StackOverflow discussions related to this topic yet most involve a code-based solution, but based upon the quote above I should be able to accomplish this via simple configuration.

Per http://wiki.fasterxml.com/JacksonFAQDateHandling I have my configuration set so that writing dates as timestamps is false. The result is that java.util.Date types are serialized to ISO 8601 format, but org.joda.time.DateTime types are serialized to a long object representation.

My environment is this:

Jackson 2.1
Joda time 2.1
Spring 3.2
Java 1.6

My Spring configuration for the jsonMapper bean is

@Bean
public ObjectMapper jsonMapper() {
    ObjectMapper objectMapper = new ObjectMapper();

    //Fully qualified path shows I am using latest enum
    ObjectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.
        WRITE_DATES_AS_TIMESTAMPS , false);

    return objectMapper;
}

My test code snippet is this

Date d = new Date();
DateTime dt = new DateTime(d); //Joda time 
Map<String, Object> link = new LinkedHashMap<String, Object>();
link.put("date", d);
link.put("createdDateTime", dt);

The resulting snippet of JSON output is this:

{"date":"2012-12-24T21:20:47.668+0000"}

{"createdDateTime": {"year":2012,"dayOfMonth":24,"dayOfWeek":1,"era":1,"dayOfYear":359,"centuryOfEra":20,"yearOfEra":2012,"yearOfCentury":12,"weekyear":2012,"monthOfYear":12 *... remainder snipped for brevity*}}

My expectation is that the DateTime object should matche that of the Date object based upon the configuration. What am I doing wrong, or what am I misunderstanding? Am I reading too much into the word automatically from the Jackson documentation and the fact that a string representation was produced, albeit not ISO 8601, is producing the advertised automatic functionality?

Jeff M
  • 1,055
  • 1
  • 10
  • 22
  • This seems to have been resolved, but it is worth pointing out that automatic Date support refers to types included in JDK -- Jackson does not by default support Date types of external Date libraries such as Joda. Support is added via extension modules (for Joda via https://github.com/FasterXML/jackson-datatype-joda) – StaxMan Jan 03 '13 at 19:20
  • @StaxMan, I definitely agree. Being new to Jackson, the “DateTime can be **automatically** serialized/deserialized similar to how java.util.Date is handled” statement in the FAQ led me to believe that this was core functionality. When I posted to the Jackson user mailing list, someone directed me to the github project link that you mentioned, and there in the usage notes is where I discovered that the JodaModule (provided via the jackson-datatype-joda project) is required to handle Joda DateTime and that the module must be registered with the ObjectMapper as shown in the answer. – Jeff M Jan 03 '13 at 21:48
  • 1
    Ok, I will update that Wiki page to mention that the support for Jackson 2.x differs from 1.x. Thanks! – StaxMan Jan 04 '13 at 18:59
  • Where are the code-based solutions?? – orbfish Mar 03 '15 at 21:54

4 Answers4

52

I was able to get the answer to this from the Jackson user mailing list, and wanted to share with you since it is a newbie issue. From reading the Jackson Date FAQ, I did not realize that extra dependencies and registration are required, but that is the case. It is documented at the git hub project page here https://github.com/FasterXML/jackson-datatype-joda

Essentially, I had to add another dependency to a Jackson jar specific to the Joda data type, and then I had to register the use of that module on the object mapper. The code snippets are below.

For my Jackson Joda data type Maven dependency setup I used this:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-joda</artifactId>
    <version>${jackson.version}</version>
</dependency>

To register the Joda serialization/deserialization feature I used this:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JodaModule());
objectMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.
    WRITE_DATES_AS_TIMESTAMPS , false);
Jeff M
  • 1,055
  • 1
  • 10
  • 22
  • 10
    Where do you put this code? Because my Spring 4.0.1 seems to use different instance of ObjectMapper (created in AllEncompassingFormHttpMessageConverter, see new MappingJackson2HttpMessageConverter) – Dzmitry Lazerka Feb 01 '14 at 03:08
  • 2
    If you were to use `Jackson2ObjectMapperFactoryBean` from Spring you wouldn't need to register `JodaModule` explicitly. It uses `Jackson2ObjectMapperBuilder` internally which registers the module automatically if it's available on the classpath. – Marcel Stör May 15 '15 at 07:09
  • 2
    Most probably you will also need to add `objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"));` like described in http://stackoverflow.com/questions/3269459/how-to-serialize-joda-datetime-with-jackson-json-processer/15605404#comment17457955_10835334 – raisercostin Aug 06 '16 at 19:59
10

Using Spring Boot.

Add to your Maven configuration...

<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-joda</artifactId>
  <version>2.7.5</version>
</dependency>

Then to your WebConfiguration...

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter
{
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
    {
      final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
      final ObjectMapper objectMapper = new ObjectMapper();

      //configure Joda serialization
      objectMapper.registerModule(new JodaModule());
      objectMapper.configure(
          com.fasterxml.jackson.databind.SerializationFeature.
            WRITE_DATES_AS_TIMESTAMPS , false);

      // Other options such as how to deal with nulls or identing...
      objectMapper.setSerializationInclusion (
         JsonInclude.Include.NON_NULL);
      objectMapper.enable(SerializationFeature.INDENT_OUTPUT);

      converter.setObjectMapper(objectMapper);
      converters.add(converter);
      super.configureMessageConverters(converters);
    }
}
Dherik
  • 17,757
  • 11
  • 115
  • 164
kervin
  • 11,672
  • 5
  • 42
  • 59
  • Works for me on mongo db `"timeStamp":1504051744068, ` is now correct as `"timeStamp":"2017-10-03T20:26:04.127Z",` – ken Oct 23 '17 at 11:09
5

In Spring Boot the configuration is even simpler. You just declare Maven dependency

    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-joda</artifactId>
    </dependency>

and then add configuration parameter to your application.yml/properties file:

spring.jackson.serialization.write-dates-as-timestamps: false
Igor Bljahhin
  • 939
  • 13
  • 23
2

I thought I'd post an updated working example using: Spring 4.2.0.RELEASE, Jackson 2.6.1, Joda 2.8.2

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/util  http://www.springframework.org/schema/util/spring-util.xsd
        ">

    <!-- DispatcherServlet Context: defines this servlet's request-processing 
        infrastructure -->

    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven>
        <message-converters>
            <beans:bean
                class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <beans:property name="objectMapper" ref="objectMapper" />
            </beans:bean>
        </message-converters>
    </annotation-driven>

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving 
        up static resources in the ${webappRoot}/resources directory -->
    <resources mapping="/resources/**" location="/resources/" />

    <!-- Resolves views selected for rendering by @Controllers to .jsp resources 
        in the /WEB-INF/views directory -->
    <beans:bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>

    <beans:bean id="objectMapper"
        class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
        <beans:property name="featuresToDisable">
            <beans:array>
                <util:constant
                    static-field="com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS" />
            </beans:array>
        </beans:property>
        <beans:property name="modulesToInstall"
            value="com.fasterxml.jackson.datatype.joda.JodaModule" />
    </beans:bean>

    <beans:bean id="localeResolver"
        class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
        <beans:property name="defaultLocale" value="en" />
    </beans:bean>

    <!-- Configure the Message Locale Resources -->
    <beans:bean id="messageSource"
        class="org.springframework.context.support.ResourceBundleMessageSource">
        <beans:property name="basename" value="errors" />
    </beans:bean>

    <beans:bean id="versionSource"
        class="org.springframework.context.support.ResourceBundleMessageSource">
        <beans:property name="basename" value="version" />
    </beans:bean>


    <!-- DataSource -->
    <beans:bean id="dataSource"
        class="org.springframework.jndi.JndiObjectFactoryBean">
        <beans:property name="jndiName" value="java:comp/env/jdbc/TestDB" />
    </beans:bean>


    <!-- POJO: Configure the DAO Implementation -->
    <beans:bean id="publicationsDAO"
        class="com.test.api.publication.PublicationsDAOJdbcImpl">
        <beans:property name="dataSource" ref="dataSource" />
    </beans:bean>


    <!-- Things to auto-load -->
    <context:component-scan base-package="com.test.api" />
    <context:component-scan base-package="com.test.rest" />

</beans:beans>

API Code

package com.test.api.publication;

import java.util.Map;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;

@JsonRootName("event")
@JsonIgnoreProperties(ignoreUnknown=true)
public class Publication {

    private Map<String, Object> tokens;
    private String href;
    private String policy_path;

    @JsonProperty("tokens")
    public Map<String, Object> getTokens() {
        return tokens;
    }

    @JsonProperty("tokens")
    public void setTokens(Map<String, Object> tokens) {
        this.tokens = tokens;
    }

    @JsonProperty("href")
    public String getHref() {
        return href;
    }

    @JsonProperty("href")
    public void setHref(String href) {
        this.href = href;
    }

    @JsonProperty("policyPath")
    public String getPolicyPath() {
        return policy_path;
    }

    @JsonProperty("policyPath")
    public void setPolicyPath(String policy_path) {
        this.policy_path = policy_path;
    }


}


package com.test.api.publication;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PublicationsDAOJdbcImpl implements PublicationsDAO{

    static final Logger logger = LoggerFactory.getLogger(PublicationsDAOJdbcImpl.class.getName());
    private DataSource _dataSource;

    @Override
    public void setDataSource(DataSource ds) {
        // TODO Auto-generated method stub

    }

    @Override
    public void close() {
        // TODO Auto-generated method stub

    }

    @Override
    public Publication getPublication(String policyPath) {
        Publication ret = new Publication();
        //TODO: do something
        return ret;
    }

}



package com.test.rest.publication;

import java.util.HashMap;

import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.test.api.publication.Publication;
import com.test.api.publication.PublicationsDAO;
import com.test.rest.error.UnknownResourceException;

@RestController
@RequestMapping("/pub")
public class PublicationController {

        private static final Logger logger = LoggerFactory.getLogger(PublicationController.class);

        @Autowired
        @Qualifier("publicationsDAO")
        private PublicationsDAO publicationsDAO;

        /**********************************************************************************************************************
         * 
         * @param policyPath
         * @return
         * @throws UnknownResourceException
         */
        @RequestMapping(value = "/{policyPath}", method = RequestMethod.GET)
        public Publication getByPolicyPath(@PathVariable String policyPath) throws UnknownResourceException{
            logger.debug("policyPath=" + policyPath);

            Publication ret = publicationsDAO.getPublication(policyPath);

            HashMap<String, Object> map = new HashMap<String, Object>();
            map.put("TEST1", null);
            map.put("TEST2", new Integer(101));
            map.put("TEST3", "QuinnZilla");
            map.put("TEST4", new DateTime());
            ret.setTokens(map);
            return ret;

        }

}

And I get the output result

{
  "tokens": {
    "TEST2": 101,
    "TEST3": "QuinnZilla",
    "TEST4": "2015-10-06T16:59:35.120Z",
    "TEST1": null
  },
  "href": null,
  "policyPath": null
}
ham-sandwich
  • 3,975
  • 10
  • 34
  • 46
  • All of that is required to convert a JodaTime DateTime instance to an ISO8601 String ? Not sure Spring is making that easier or *harder*... – Darrell Teague May 02 '16 at 17:00