4

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.

Anders
  • 125
  • 1
  • 3
  • 13

2 Answers2

7

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.

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Can I store this custom provider/ContextResolver anywhere in the package containing the rest application and then Jackson will automatically detect it and use it instead of it's default ContextResolver implementation? Sorry if this is a stupid question, but I have never implemented any custom JAX-RS interfaces before. – Anders Feb 11 '15 at 11:59
  • If all your resource classes are being picked up by package scanning (meaning you don't explicitly register them), then yes this provider should be pick up automatically (because of the @Provider annotation). Just put it in a package on the same level or or in a sublevel of the package you specified for scanning. If you haven't specified a path, and your resources are still picked up, then probably you have the entire classpath being scanned. In which case, it doesn't matter what package its in. And yes, Jackson provides a MessageBodyReader, that will call this ContextResolver – Paul Samsotha Feb 11 '15 at 12:02
  • Hmm. When I implement a custom ContextResolver as you suggested I can clearly see that the getContext method and constructor is called on each request. The JSON output however remains the same. Still it would appear to be the right solution so I think I'll mark the question as solved. Thank you :) – Anders Feb 11 '15 at 15:18
  • The ContextResolver does remove NULL_VALUES as expected when I add the line "mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);" so it's only the Date objects it's struggling with. When you tested this did you use the same application server? If you used something else can you post your POM or list your server's included libraries. I'm starting to wonder if there is a bug in one of the libraries included in my project. – Anders Feb 11 '15 at 16:58
  • Did you include any actual Jackson dependencies? I've seen people accidently mix Jackson 1 and 2 and they would use 1 annotations and 2 ObjectMapper, then wonder why the annotations were not working. As for the pom, all I have is the `jersey-media-json-jackson:2.15` and `jersey-container-servlet:2.15`. That's all you need to get a jersey webapp running – Paul Samsotha Feb 12 '15 at 00:09
  • Also see [`mapper.setSerializationInclusion(JsonInclude...)`](http://fasterxml.github.io/jackson-databind/javadoc/2.2.0/com/fasterxml/jackson/databind/ObjectMapper.html#setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include). You can configure the mapper instead of using annotations – Paul Samsotha Feb 12 '15 at 00:17
  • Both annotations and the mapper works for any object except Date objects. It looks like something is happening before the Date object is handed over to Jackson for processing. – Anders Feb 12 '15 at 10:40
  • BTW: Which application server are you using? I might give it a try to see if there are conflicting libraries included with mine. – Anders Feb 12 '15 at 10:55
  • I just ran it in a standalone Grizzly container. I will post a simple complete example to show it works. – Paul Samsotha Feb 12 '15 at 11:00
  • See my Update.. Just run that one class as a JUnit test – Paul Samsotha Feb 12 '15 at 11:04
  • You know what. I just discovered something. I think the default _is_ to use timestamps. I commented out the configuration and it still sent it out as a timestamp. I guess my answer is wrong then. Weird. I could've sworn yesterday, it didn't behave this way. Maybe I was mistaken – Paul Samsotha Feb 12 '15 at 11:11
  • I don't know what could be the problem then. I thought maybe a different provider is being used, like MOXy (if you are using Glassfish). But you say your Jackson annotations are working. Are you using a lot of annotations? I'm just wondering if maybe the default configuration of a different provider just makes it seem like your annotations are working. For instance MOXy has some defaults like ignoring nulls. If you are in Glassfish, then MOXy will be used by default if you don't configure another provider. Like `register(JacksonFeature.class)`. – Paul Samsotha Feb 12 '15 at 11:16
  • I'm using jetty(-runner). It contains no default implementation of jersey or MOXy/Jackson. Reading the Jackson wiki I also read that timestamps was on by default, but I just assumed that this had been changed in a later version. After noticing that no annotation, or change to the mapper configuration, affected any of the Date objects I started to wonder if something was altering the objects before sending them to the mapper. Like I said earlier everything else is working regardless if I set it through the mapper or via an annotation. – Anders Feb 12 '15 at 14:10
  • Yeah I am not sure. I would need to be able to test a complete example that demonstrate the problem. I doubt any library is causing this. Can you provide enough information to reproduce the problem. What I would do is create a new application, in the most simplest form, that reproduces the problem. Sometimes you end up finding out the problem yourself this way. when you get a product that reproduces the problem, if you can't figure it out, them post all the necessary steps to reproduce it, i.e. app config, depedencies, resource class, how you're calling the server, etc. Key word is "minimal". – Paul Samsotha Feb 12 '15 at 23:29
  • This is how I actually end up solving most of my own problems :-) – Paul Samsotha Feb 12 '15 at 23:31
  • The issue turned out to be completely unrelated to Jackson annotations or the ObjectMapperContextResolver. I was making an implicit conversion from java.sql.Date to java.util.Date when setting the birthDate. Although the sql.Date is a specialization of the util.Date Jackson is unable to serialize it in the same manner as it would with a java.util.Date created using the constructor. – Anders Feb 13 '15 at 11:49
6

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;
}
Brice Roncace
  • 10,110
  • 9
  • 60
  • 69
  • Thanks. I wonder if there's more standard way to force Jackson to treat child class as father class, but this works for me for now. – John Feb 08 '17 at 09:07