Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I have JAX-RS return a Java 8 LocalDateTime property as a JavaScript-style Date String?

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?

like image 873
Joe7 Avatar asked Nov 07 '14 15:11

Joe7


People also ask

How do I get LocalDateTime in Java 8?

You can get the time from the LocaldateTime object using the toLocalTime() method. Therefore, another way to get the current time is to retrieve the current LocaldateTime object using the of() method of the same class. From this object get the time using the toLocalTime() method.

What is the default format of LocalTime in Java 8?

LocalTime LocalTime is an immutable class whose instance represents a time in the human readable format. It's default format is hh:mm:ss. zzz.

How does JSON define LocalDate?

To configure Jackson to map a LocalDate into a String like 1982-06-23 , you need to activate the JavaTimeModule . You can register the module with a Jackson ObjectMapper instance like this: ObjectMapper mapper = new ObjectMapper(); mapper. registerModule(new JavaTimeModule()); mapper.


2 Answers

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.

like image 65
Paul Samsotha Avatar answered Oct 18 '22 19:10

Paul Samsotha


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).

like image 26
assylias Avatar answered Oct 18 '22 18:10

assylias