Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Efficient way to have Jackson serialize Java 8 Instant as epoch milliseconds?

Using Spring RestControllers with Jackson JSON parsing backend, with AngularJS on front end. I'm looking for an efficient way to have Jackson serialize an Instant as the epoch milliseconds for subsequent convenient usage with JavaScript code. (On the browser side I wish to feed the epoch ms through Angular's Date Filter: {{myInstantVal | date:'short' }} for my desired date format.)

On the Java side, the getter that Jackson would use is simply:

public Instant getMyInstantVal() { return myInstantVal; }

Serialization wouldn't work as-is, because the jackson-datatype-jsr310 doesn't return Epoch milliseconds by default for an Instant. I looked at adding @JsonFormat to the above getter to morph the Instant into something the front-end can use, but it suffers from two problems: (1) the pattern I can supply it is apparently limited to SimpleDateFormat which doesn't provide an "epoch milliseconds" option, and (2) when I tried to send the Instant as a formatted date to the browser instead, Jackson throws an exception because the @JsonFormat annotation requires a TimeZone attribute for Instants, something I don't wish to hardcode as it would vary from user to user.

My solution so far (and it's working fine) is to create a replacement getter using @JsonGetter, which causes Jackson to use this method instead to serialize myInstantVal:

@JsonGetter("myInstantVal")
public long getMyInstantValEpoch() {
    return myInstantVal.toEpochMilli();
}

Is this the proper way of doing this? Or is there a nice annotation I'm missing that I can put on getMyInstantVal() so I won't have to create these additional methods?

like image 455
Glen Mazza Avatar asked Jun 23 '16 19:06

Glen Mazza


People also ask

How does Jackson deserialize dates from JSON?

In order to correct deserialize a Date field, you need to do two things: 1) Create a custom deserializer by extending StdDeserializer<T> class and override its deserialize(JsonParser jsonparser, DeserializationContext context) method. This method should return a Date if we are using this to parse a date field in JSON.

How does Jackson serialize date?

It's important to note that Jackson will serialize the Date to a timestamp format by default (number of milliseconds since January 1st, 1970, UTC).

Does Jackson use Java serialization?

XML Serialization and Deserialization with Jackson This short tutorial shows how the Jackson library can be used to serialize Java object to XML and deserialize them back to objects.


2 Answers

You just need to read the README that you linked to. Emphasis mine:

Most JSR-310 types are serialized as numbers (integers or decimals as appropriate) if the SerializationFeature#WRITE_DATES_AS_TIMESTAMPS feature is enabled, and otherwise are serialized in standard ISO-8601 string representation.

[...]

Granularity of timestamps is controlled through the companion features SerializationFeature#WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS and DeserializationFeature#READ_DATE_TIMESTAMPS_AS_NANOSECONDS. For serialization, timestamps are written as fractional numbers (decimals), where the number is seconds and the decimal is fractional seconds, if WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS is enabled (it is by default), with resolution as fine as nanoseconds depending on the underlying JDK implementation. If WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS is disabled, timestamps are written as a whole number of milliseconds.

like image 135
JB Nizet Avatar answered Sep 19 '22 12:09

JB Nizet


Adding on to JB's answer, to override Spring MVC's default JSON parser to strip away the nanoseconds from Instant (and other Java 8 date objects that have them):

  1. In the mvc:annotation-driven element, specify that you will be overriding the default JSON message converter:

    <mvc:annotation-driven validator="beanValidator"> <mvc:message-converters register-defaults="true"> <beans:ref bean="jsonConverter"/> </mvc:message-converters> </mvc:annotation-driven>

(register-defaults above is true by default and most probably what you'll want to keep the other converters configured by Spring as-is).

  1. Override MappingJackson2HttpMessageConverter as follows:

    <beans:bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
    <beans:property name="objectMapper">
        <beans:bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
            <beans:property name="featuresToDisable">
                <beans:array>
                    <util:constant static-field="com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS"/>
                </beans:array>
            </beans:property>
        </beans:bean>
    </beans:property>
    

Step #1 is important as Spring MVC will otherwise ignore the configured MJ2HMC object in favor of its own default one.

partial H/T this SO post.

like image 28
Glen Mazza Avatar answered Sep 18 '22 12:09

Glen Mazza