10

I created a RESTful web service using JAX-RS method annotations:

@GET
@Path("/test")
@Produces(MediaType.APPLICATION_JSON)
public MyThing test()
{
    MyThing myObject = new MyThing(LocalDateTime.now());
    return myObject;
}

This works nicely, but I'd like to adjust one thing: If the returned Java object contains a property of the new Java 8 LocalDateTime type, it is represented as a JSON object:

{"myDateTimeProperty":{"hour":14,"minute":32,"second":39,"year":2014,"month":"NOVEMBER","dayOfMonth":6,"dayOfWeek":"THURSDAY","dayOfYear":310,"monthValue":11,"nano":0,"chronology":{"calendarType":"iso8601","id":"ISO"}},...}

How can I tell JAX-RS to return a JavaScript Date.toJSON()-style String like

{"myDateTimeProperty":"2014-11-07T15:06:36.545Z",...}

instead?

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
Joe7
  • 508
  • 1
  • 4
  • 17

3 Answers3

10

Note: See UPDATE below

I've never use LocalDateTime before, so I decided to do some testing. Here are my findings:

  • Jersy 2.13 and this provider (works out the box with no extra configuration)

    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-moxy</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    
  • Jersey 2.13 with this provider (has support for JAXB annotation - dependency on jackson-module-jaxb-annotations), with custom adapter

    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    
    public class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime> {
        @Override
        public LocalDateTime unmarshal(String s) throws Exception {
            return LocalDateTime.parse(s);
        }
        @Override
        public String marshal(LocalDateTime dateTime) throws Exception {
            return dateTime.toString();
        }   
    }
    
    // Getter for model class
    @XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
    public LocalDateTime getDateTime() {
        return dateTime;
    }
    
  • Resteasy 3.0.9 with this provider, (also has support for JAXB annotation - dependency on jackson-module-jaxb-annotations), with custom adapter (See above)

    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-jackson2-provider</artifactId>
        <version>${resteasy.version}</version>
    </dependency>
    
  • Both Resteasy and Jersey with this dependency (also did not work without custom config, same as last two - with adapter)

    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-json-provider</artifactId>
        <version>2.4.0</version>
    </dependency>
    

    We need to make sure to register the JacksonJaxbJsonProvider


So I guess it seems that any provider that uses Jackson, does not give you the deisred result, without some custom configuration, whether its through an adapter (as seen above) or some other custom configuration. The jersey-media-moxy provider doesn't use Jackson.


UPDATE

For the most part, the information above is incorrect.

  • MOXy does not work by default. It works for serialization by simply calling toString(), which may or may not be what you want, and it won't work when de-serializing. If you are using MOXy, until it supports Java8 time, you will need to use an XMLAdapter

  • Jackson you will need to configure its Java8 time support. This is the case with both Jersey and RESTEasy.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • 1
    Since I'm already using Resteasy, I just added a `LocalDateTimeAdapter` as in your example. `dateTime.toString()` returns Strings like `2014-11-06T22:45:47`, which is close but still not what I need, but some extra formatting like `DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); return formatter.format(dateTime);` did the job for me. Just one thing left: What exactly do you mean by "some other custom configuration"? Anything specific to avoid having to annotate all LocalDateTime getters and setters? – Joe7 Nov 09 '14 at 19:57
  • 1
    @Joe7 Sorry I should've stated "... some other configuration _I may be oblivious to_" :-) (I was implying that maybe there is some configuration with the provider that may be possible) - But to avoid annotating the methods, you could declare/bind the adapter at the package level, as seen [here](http://blog.bdoughan.com/2011/05/jaxb-and-joda-time-dates-and-times.html), but I am unaware of any other non annotation configurations to handle this. My apologies. – Paul Samsotha Nov 10 '14 at 06:14
  • 2
    Great, I included the adapters as package-level annotations now, this works great for me. It may be noteworthy that package level annotations must be included in a separate package-info.java file rather than any particular class file. – Joe7 Nov 13 '14 at 18:25
  • Did you try consuming an object which has a LocalDateTime (or some other java 8 date time object) as a field? I have a grizzly/jersey setup which gives null for all LoalDateTime fields in objects I POST. The input I give has the exact same format as what is produced by a method which returns the same object, e.g. `{"time":"2015-03-22T19:55:26.075Z","value":"foobar"}` – zpon Mar 22 '15 at 19:57
  • @zpon: Yes, consuming POSTed DateTime fields works fine for me with this solution as well. I'm using RESTeasy in a WildFly 8.2 server. Sounds like you should take your issue to a separate question. – Joe7 Apr 23 '15 at 20:55
7

I don't remember the exact details of why I did this but I got it working with the following dependencies:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.4.2</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
  <version>2.4.2</version>
</dependency>

And the provider below:

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class JacksonContextResolver implements ContextResolver<ObjectMapper> {
  private static final ObjectMapper om = init();

  @Override public ObjectMapper getContext(Class<?> objectType) {
    return om;
  }

  private static ObjectMapper init() {
    ObjectMapper om = new ObjectMapper();
    om.registerModule(new JavaTimeModule());
    return om;
  }
}

Note that you can also play with the following flag:

om.configure(WRITE_DATES_AS_TIMESTAMPS, true/false);

(the actual class has more stuff in it but I believe it is irrelevant to this answer).

assylias
  • 321,522
  • 82
  • 660
  • 783
  • 1
    Already getting closer... now the method returns an array of numbers instead of a JSON object: `{"myDateTimeProperty":[2014,11,6,14,32,39],...}` Unfortunately, playing with the WRITE_DATES_AS_TIMESTAMPS flag didn't help, and I don't see any other flag that seems to be related. – Joe7 Nov 08 '14 at 19:32
  • FYI, should use JavaTimeModule instead of JSR310Module (deprecated) – wheeleruniverse Jul 25 '18 at 17:27
0

UPDATED: works with: <version>2.4.2</version>

When I try the above:

Caused by: java.lang.NoSuchMethodError: com.fasterxml.jackson.datatype.jsr310.ser.JSR310FormattedSerializerBase.findFormatOverrides(Lcom/fasterxml/jackson/databind/SerializerProvider;Lcom/fasterxml/jackson/databind/BeanProperty;Ljava/lang/Class;)Lcom/fasterxml/jackson/annotation/JsonFormat$Value;

I get the following error. Any ideas plse?

<jackson.version>2.8.0</jackson.version>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>${jackson.version}</version>

package com.jobs.spring.configuration;

import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class JacksonContextResolver implements ContextResolver<ObjectMapper> {
    private static final ObjectMapper om = init();

    @Override
    public ObjectMapper getContext(Class<?> objectType) {
        return om;
    }

    private static ObjectMapper init() {
        ObjectMapper om = new ObjectMapper();
        om.registerModule(new JSR310Module());
        return om;
    }
}

Also tried with the following as JSR310Module() is deprecicated in 2.8.0

om.registerModule(new JavaTimeModule());
Richard
  • 8,193
  • 28
  • 107
  • 228