11

This is my user class, and I to save ISO compliant date time in my database.

public class User  {

    @Id
    private String id;

    private String email;

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime loginDate;

 }

Here is my Jersey controller:

@POST
@Consumes("application/json")
@Produces("application/json")

public Response create(  User user) {

    Map<Object, Object> apiResponse = new HashMap<Object, Object>();
    Map<Object, Object> response  = new HashMap<Object, Object>();


    user = (User) userService.create(user);

}

How can can I consume a datetime format like this one in jersey? Is it possible to send a datatime String and create Java 8 date time object automatically?

{        
    "email" : "imz.mrz@gmail.com"
    "loginDate" : "2015-04-17T06:06:51.465Z"
} 
#

Update:

I was using Spring boot jersey, and had other jsr packages

  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-jersey</artifactId>
 </dependency>

So I removed all the packages except from spring-boot-jersey package. use this annotation for LocalDateTime

  @JsonDeserialize(using =  LocalDateTimeDeserializer.class)

This way I can consume ISODate and save ISODate() to mongodb and produce full formated mongodb LocalDateTime to frontend.

Problem solved.

Imtiaz Mirza
  • 591
  • 1
  • 7
  • 26
  • There's probably a library that someone has written to do this, if not, you might need to write your own custom converter. See http://blog.dejavu.sk/2014/02/11/inject-custom-java-types-via-jax-rs-parameter-annotations/ – BretC Apr 19 '15 at 22:39

1 Answers1

17

Couple options I see...

Option 1:

Assuming you have JAXB annotation support with Jackson as the JSON provider...

You could use an XmlAdapter. For example

public class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime> {

    @Override
    public LocalDateTime unmarshal(String dateString) throws Exception {
        Instant instant = Instant.parse(dateString);
        LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        return dateTime;
    }

    @Override
    public String marshal(LocalDateTime dateTime) throws Exception {
        Instant instant = dateTime.toInstant(ZoneOffset.UTC);
        return DateTimeFormatter.ISO_INSTANT.format(instant);
    }
}

See the Instant API for more information.

Then you can just annotate the field/property with the adapter

@XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
private LocalDateTime loginDate;

You could also declare the annotation at the package level, so that all uses in the package will use the adapter, without the need to annotate. You do so in a file named package-info.java put inside the package

@XmlJavaTypeAdapters({
    @XmlJavaTypeAdapter(type = LocalDateTime.class, 
                        value = LocalDateTimeAdapter.class)
})
package thepackage.of.the.models;

import java.time.LocalDateTime;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;

Option 2:

Use the Jackson APIs directly. Meaning, use a JsonDeserializer and JsonSerializer. For example

public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {

    @Override
    public LocalDateTime deserialize(JsonParser jp, 
            DeserializationContext dc) throws IOException, JsonProcessingException {
        ObjectCodec codec = jp.getCodec();
        TextNode node = (TextNode)codec.readTree(jp);
        String dateString = node.textValue();
        Instant instant = Instant.parse(dateString);
        LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        return dateTime;
    } 
}

public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {

    @Override
    public void serialize(LocalDateTime dateTime, JsonGenerator jg, 
            SerializerProvider sp) throws IOException, JsonProcessingException {
        Instant instant = dateTime.toInstant(ZoneOffset.UTC);
        jg.writeString(DateTimeFormatter.ISO_INSTANT.format(instant));
    } 
}

You can apply this at the field/property level

@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
public LocalDateTime loginDate; 

Or at the ObjectMapper level (so you don't need to annotate everywhere)

@Provider
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {

    final ObjectMapper mapper = new ObjectMapper();

    public ObjectMapperContextResolver() {
        SimpleModule module = new SimpleModule();
        module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
        module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
        mapper.registerModule(module);
        // add JAXB annotation support if required
        mapper.registerModule(new JaxbAnnotationModule());
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return mapper;
    }  
}

Basically what happens, is that the MessageBodyWriter/MessageBodyReader used for ummarshalling/marshalling, will call the getContext method to get the ObjectMapper

Note:

  • The above solutions will parse from the format 2007-12-03T10:15:30.00Z, as documented in Instant.parse, and will serialize to the same format, as documented in DateTimeFormatter.ISO_INSTANT

  • The above is also assuming you are using Jackson as the Serializer. I used the below dependency (with Jersey 2.16) to test

    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>2.16</version>
    </dependency>
    

    The dependency uses a JacksonJaxbJsonProvider for JAXB annotation support. If you are using a lower version of Jersey like 1.x, the jersey-json dependency should offer JAXB annotation support, if you enable the POJO mapping feature. Alternatively for Jersey 1.x, if you want to use Jackson 2, you can use this dependency

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

    which is actually what is used by jersey-media-json-jackson. So you could explicitly register the JacksonJaxbJsonProvider, or add the Jackson package (com.fasterxml.jackson.jaxrs.json) to list packages to scan


UPDATE

See Also:

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Thanks for very descriptive answer, I'm going with option 2. Now I can save the it in mongodb with this format "loginDate" : { "dateTime" : ISODate("2015-04-17T10:06:51.465Z"), "offset" : { "_id" : "Z", "totalSeconds" : 0 } } However when try to Serilalize it it doesn't work. It gives me "No property null found on entity class java.time.OffsetDateTime to bind constructor parameter to!" – Imtiaz Mirza Apr 20 '15 at 02:22
  • Changed the it LocalDateTime, still getting the error when try to retrieve the Object "No property null found on entity class java.time.LocalDateTime to bind constructor parameter to" – Imtiaz Mirza Apr 20 '15 at 02:42
  • 1
    It work fine for me. Which configuration are you using? Annotation or ObjectMapper? – Paul Samsotha Apr 20 '15 at 02:48
  • 2
    In the `ContextResolver` I forgot the register the `SimpleModule` :-) `mapper.registerModule(module);` Made edit. – Paul Samsotha Apr 20 '15 at 02:53
  • I'm using annotations , if I use objectmapper how should I use it ? – Imtiaz Mirza Apr 20 '15 at 03:04
  • 1
    The annotations should work. I've tested all the options above. As for how to use the `ObjectMapper`, if you are scanning for all your resource classes (ones annotated with `@Path`), then the `ContextResolver` should automatically be picked up with the `@Provider` annotation. No extra configuration is required. I would put a print statement in the `getContext` method to see if it is called when serializing. Let me know. Also have you tried deserializing? Does _that_ work? – Paul Samsotha Apr 20 '15 at 03:07
  • yes deserializing works. About serializing I don't think its a jersey issue, with this case we are saving the data as ISO() format. Mongo db can't retrieve the data. However thanks for your help – Imtiaz Mirza Apr 20 '15 at 03:33
  • Cleaning up all jersey related packages except com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.5.2 Solved the problem for me. I ended up not using serialization. Thanks for your help. – Imtiaz Mirza Apr 22 '15 at 01:23