How to set format of string for java.time.Instant using objectMapper?
Asked Answered
B

7

85

I have an entity with java.time.Instant for created data field:

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Item {
    private String id;
    private String url;
    private Instant createdDate;
}

I am using com.fasterxml.jackson.databind.ObjectMapper to save item to Elasticsearch as JSON:

bulkRequestBody.append(objectMapper.writeValueAsString(item));

ObjectMapper serializes this field as an object:

"createdDate": {
    "epochSecond": 1502643595,
    "nano": 466000000
}

I was trying the annotation @JsonFormat(shape = JsonFormat.Shape.STRING) but it doesn't work for me.

My question is how I could serialize this field as 2010-05-30 22:15:52 string?

Bolitho answered 13/8, 2017 at 17:4 Comment(4)
github.com/FasterXML/jackson-modules-java8/tree/master/datetimeWeigle
@JBNizet Thank you for answer, I have added objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); and objectMapper.findAndRegisterModules() It does not work for me.Bolitho
Have you added the module jar file to your classpath?Weigle
Sure, compile group: 'com.fasterxml.jackson.module', name: 'jackson-modules-java8', version: '2.9.0', ext: 'pom' this oneBolitho
A
164

One solution is to use jackson-modules-java8. Then you can add a JavaTimeModule to your object mapper:

ObjectMapper objectMapper = new ObjectMapper();

JavaTimeModule module = new JavaTimeModule();
objectMapper.registerModule(module);

By default the Instant is serialized as the epoch value (seconds and nanoseconds in a single number):

{"createdDate":1502713067.720000000}

You can change that by setting in the object mapper:

objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

This will produce the output:

{"createdDate":"2017-08-14T12:17:47.720Z"}

Both formats above are deserialized without any additional configuration.

To change the serialization format, just add a JsonFormat annotation to the field:

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
private Instant createdDate;

You need to set the timezone, otherwise the Instant can't be serialized properly (it throws an exception). The output will be:

{"createdDate":"2017-08-14 12:17:47"}

Another alternative, if you don't want to (or can't) use java8 modules, is to create a custom serializer and deserializer, using a java.time.format.DateTimeFormatter:

public class MyCustomSerializer extends JsonSerializer<Instant> {

    private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneOffset.UTC);

    @Override
    public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
        String str = fmt.format(value);

        gen.writeString(str);
    }
}

public class MyCustomDeserializer extends JsonDeserializer<Instant> {

    private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneOffset.UTC);

    @Override
    public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        return Instant.from(fmt.parse(p.getText()));
    }
}

Then you annotate the field with those custom classes:

@JsonDeserialize(using = MyCustomDeserializer.class)
@JsonSerialize(using = MyCustomSerializer.class)
private Instant createdDate;

The output will be:

{"createdDate":"2017-08-14 12:17:47"}

One detail is that in the serialized string you're discarding the fraction of second (everything after the decimal point). So, when deserializing, this information can't be recovered (it'll be set to zero).

In the example above, the original Instant is 2017-08-14T12:17:47.720Z, but the serialized string is 2017-08-14 12:17:47 (without the fraction of seconds), so when deserialized the resulting Instant is 2017-08-14T12:17:47Z (the .720 milliseconds are lost).

Antrim answered 14/8, 2017 at 12:30 Comment(6)
What format string would I have to use in case I would like to keep fraction of seconds? Thx.Elwood
How to disable conversion of nano sec. I do not want to send decimal number. instead just need mili second numberManning
JsonFormat annotation didn't work for me. but the JavaTimeModule did..Rudy
Didn't work for me with java 8 modules and WRITE_DATES_AS_TIMESTAMPS false, I get the error no String-argument constructor/factory method to deserialize from String value ('2020-08-13T10:46:16.860Z')Pronouncement
For Jackson version > 2.10, see this answer.Gradin
Setting the timezone on JsonFormat did the trick for me (and optional milliseconds. @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss[.SSS]'Z'", timezone = "UTC")Eddings
K
11

For those looking to parse Java 8 timestamps. You need a recent version of jackson-datatype-jsr310 in your POM and have the following module registered:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

To test this code

@Test
void testSeliarization() throws IOException {
    String expectedJson = "{\"parseDate\":\"2018-12-04T18:47:38.927Z\"}";
    MyPojo pojo = new MyPojo(ZonedDateTime.parse("2018-12-04T18:47:38.927Z"));

    // serialization
    assertThat(objectMapper.writeValueAsString(pojo)).isEqualTo(expectedJson);

    // deserialization
    assertThat(objectMapper.readValue(expectedJson, MyPojo.class)).isEqualTo(pojo);
}
Kyd answered 6/12, 2018 at 17:43 Comment(1)
For Jackson version > 2.10, see this answer.Gradin
A
7

Here's some Kotlin code of formatting Instant, so it does not contain milliseconds, you can use custom date formatters

ObjectMapper().apply {
        val javaTimeModule = JavaTimeModule()
        javaTimeModule.addSerializer(Instant::class.java, Iso8601WithoutMillisInstantSerializer())
        registerModule(javaTimeModule)
        disable(WRITE_DATES_AS_TIMESTAMPS)
    }

private class Iso8601WithoutMillisInstantSerializer
        : InstantSerializer(InstantSerializer.INSTANCE, false, DateTimeFormatterBuilder().appendInstant(0).toFormatter())
Azalea answered 3/1, 2019 at 13:24 Comment(1)
This is one of the good solution, here we don't need to edit the model class and we can customise the deserializer functionality as and when needed.Rout
D
3

You need to add below dependency

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.6.5</version>
</dependency>

And then register the modules as below :

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
Dafodil answered 13/8, 2017 at 18:51 Comment(1)
Thank you, I'found this solution. But this solution seems to be deprecatedBolitho
C
2

In my case it was enough to register the JavaTimeModule:

  ObjectMapper objectMapper = new ObjectMapper();
  JavaTimeModule module = new JavaTimeModule();
  objectMapper.registerModule(module);

  messageObject = objectMapper.writeValueAsString(event);

In the event Object I have a field of type Instant.

In the deserialization you also need to register the java time module:

ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());

Event event = objectMapper.readValue(record.value(), Event.class);
Cattegat answered 24/1, 2019 at 12:8 Comment(1)
the important thing here is "new ObjectMapper().registerModule(new JavaTimeModule());should be active during serialization . Otherwise, Objectmapper will use epochSecond and nano convert the Instant data type to a nested object."Esplanade
C
1

You can use Spring ObjectMapper which already configured with JavaTimeModule. Just inject it from Spring context and don't use new ObjectMapper().

Consignee answered 15/7, 2019 at 12:46 Comment(1)
Only applicable if spring-web is on the classpath.Heterophony
H
1

If using Spring, and spring-web is on the classpath, you can create an ObjectMapper using the Jackson2ObjectMapperBuilder. It registers the following commonly used modules within the method registerWellKnownModulesIfAvailable.

com.fasterxml.jackson.datatype.jdk8.Jdk8Module
com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
com.fasterxml.jackson.datatype.joda.JodaModule
com.fasterxml.jackson.module.kotlin.KotlinModule

Some of these modules have been merged into Jackson 3; see here.

Heterophony answered 12/11, 2020 at 3:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.