Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I (un)marshall a Date as a time-stamp with jackson

I'm having trouble (un)marshalling java.util.Date objects into timestamps. Ideally the timestamps should be in a UTC-0 format and not the server's local time zone. Although I can work around that pretty easily if I need to.

NB: I am aware that here are several similar topics on stack overflow but everyone I have come across is either outdated (with respect to the API's being used) or are related to serializing Date objects to strings.

Here is an excerpt of my POM file:

<dependency>
    <groupId>javax.ws.rs</groupId>
    <artifactId>javax.ws.rs-api</artifactId>
    <version>2.0.1</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.15</version>
</dependency>

Sample model class:

public class PersonJSON {
    private Date birthDate;
}

Expected output (assuming birth date is 01 Feb 2015 00:00:00 UTC-0):

{"birthDate":1422748800000}

Current output:

{"birthDate":"2015-02-01"}

Is it possible to fix this (ideally using an annotation)?

Solution

As it turns out the error was caused by an implicit conversion from Java.sql.Date to Java.util.Date. The conversion throws no exceptions and the sql.Date extends util.Date so logically it should work, but it doesn't. The solution was to extract the timestamp from the Java.sql.Date and use it as input to the Java.util.Date constructor.

If you do want to alter the format the approaches listed by peeskillet is correct assuming you have a valid Java.util.Date object.

like image 586
Anders Avatar asked Dec 15 '22 16:12

Anders


2 Answers

You just need to configure the SerializationFeature.WRITE_DATES_AS_TIMESTAMPS on the ObjectMapper. From what I tested I didn't need to configure the deserialization, it worked fine passing a timestamp.

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {

    private final ObjectMapper mapper;

    public ObjectMapperContextResolver() {
        mapper = new ObjectMapper();
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
    }

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

}

As far as setting this feature by annotation per field, I am not sure how to do that with Jackson, without writing a custom serializer (but I wouldn't doubt if there is some other way out there). You can always use the JAXB annotation support that comes with the Jackson provider. Just write an XmlAdapter and annotate the field with @XmlJavaTypeAdatper(YourDateAdapter.class). Here's an example


UPDATE

Complete example.

Required dependencies

<dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
    <version>2.15</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.15</version>
</dependency>

Test (you need to register the context resolver from above)

import java.util.Date;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import jersey.stackoverflow.provider.ObjectMapperContextResolver;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;

public class ObjectMapperTest extends JerseyTest {

    public static class Person {
        public Date birthDate;
    }

    @Path("/person") @Produces(MediaType.APPLICATION_JSON)
    public static class PersonResource {
        @GET 
        public Response getPerson() {
            Person person = new Person();
            person.birthDate = new Date();
            return Response.ok(person).build();
        }
    }

    @Override
    public Application configure() {
        return new ResourceConfig(PersonResource.class,
                                  ObjectMapperContextResolver.class);
    }

    @Test
    public void test() {
        String personJson = target("person").request().get(String.class);
        System.out.println(personJson);
    }
}

Result: {"birthDate":1423738762437}


Update 2

I don't think this answer is correct. It seems Jackson already serializes as timestamps by default. If we didn't want timestamps, then we would set the above configuration as false. But this still does not answer the OPs question. Not sure what the problem is.

like image 180
Paul Samsotha Avatar answered Jan 22 '23 19:01

Paul Samsotha


I ran into this issue as well. In my case Hibernate ORM was loading up java.util.Date entity attributes as java.sql.Date objects which were not serializing as timestamps as I was expecting (which is the default serialization strategy employed by jackson when serializing java.util.Date objects).

My fix was to explicitly direct java.sql.Date objects to use jackson's stock com.fasterxml.jackson.databind.ser.std.DateSerializer class just like it uses for java.util.Date objects:

private Client buildClient() {
  return ClientBuilder.newBuilder()
    .register(getJacksonJsonProvider())
    .build();
}

private JacksonJsonProvider getJacksonJsonProvider() {
  JacksonJsonProvider jjp = new JacksonJaxbJsonProvider();
  jjp.setMapper(getJsonObjectMapper());
  return jjp;
}

private ObjectMapper getJsonObjectMapper() {
   ObjectMapper mapper = new ObjectMapper();
   mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
   SimpleModule module = new SimpleModule();
   module.addSerializer(java.sql.Date.class, new DateSerializer()); // <-- My Fix
   mapper.registerModule(module);
   return mapper;
}
like image 40
Brice Roncace Avatar answered Jan 22 '23 19:01

Brice Roncace