Force Milliseconds When Serializing Instant to ISO8601 using Jackson
Asked Answered
C

6

17

I have some questions related to JSON serialization using Jackson in a project where I use Spring Boot 2.0.0.M6, Spring Framework 5.0.1.RELEASE and Jackson 2.9.2.

I have configured the following Jackson-related settings in application.properties:

spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false

Serialization works mostly as I need. Nevertheless, I have noticed that Jackson seems to cut-off milliseconds if they are 000.

Test 1: Serialize Instant with milliseconds set to 000:

  • Initialize Instant field using Instant.parse("2017-09-14T04:28:48.000Z")
  • Serialize it using Jackson
  • Output will be "2017-09-14T04:28:48Z"

Test 2: Serialize Instant with milliseconds set to some non-000 value:

  • Initialize Instant field using Instant.parse("2017-09-14T04:28:48.100Z")
  • Serialize it using Jackson
  • Output will be "2017-09-14T04:28:48.100Z"

Questions:

  • Is that behavior by design?
  • Is there anything I can do to force serialization of 000?
Christen answered 26/11, 2017 at 23:32 Comment(2)
Seems similar to this github issue github.com/FasterXML/jackson-datatype-jsr310/issues/39Ikeda
@SeanCarroll Yes indeed. Thank you for pointing that out.Arnst
H
13

I solve using this aproach:

ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(Instant.class, new InstantSerializerWithMilliSecondPrecision());
objectMapper.registerModule(module);

And for InstantSerializerWithMilliSecondPrecision i used this:

public class InstantSerializerWithMilliSecondPrecision extends InstantSerializer {

    public InstantSerializerWithMilliSecondPrecision() {
        super(InstantSerializer.INSTANCE, false, new DateTimeFormatterBuilder().appendInstant(3).toFormatter());
    }
}

Now the Instant serialization always includes milliseconds. Example: 2019-09-27T02:59:59.000Z

Homework answered 24/9, 2019 at 13:14 Comment(0)
I
5

There appears to be a Jackson issue open for this here*. That link contains two workarounds

Workaround 1

 ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new JavaTimeModule());
    SimpleModule module = new SimpleModule();
    module.addSerializer(ZonedDateTime.class, new JsonSerializer<ZonedDateTime>() {
        @Override
        public void serialize(ZonedDateTime zonedDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            jsonGenerator.writeString(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZZZ").format(zonedDateTime));
        }
    });
    objectMapper.registerModule(module);
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    objectMapper.enable(SerializationFeature.INDENT_OUTPUT);

Workaround 2

JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(ZonedDateTime.class,
  new ZonedDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")));
ObjectMapper mapper = new ObjectMapper().registerModule(javaTimeModule);

*Link is dead because they deprecated FasterXML/jackson-datatype-jsr310 and moved it to jackson-modules-java8. See https://github.com/FasterXML/jackson-modules-java8/issues/76

Ikeda answered 27/11, 2017 at 4:13 Comment(5)
Unfortunately, my Instants still have no .000 when serialized. Tried your first workaround. I created a configuration class and added an ObjectMapper bean using Primary annotation to override the auto-configured object mapper.Arnst
I tried Jackson 2.9.5. and this issue is still not fixed. But your 1.st workaround works, thank youChe
I was looking here github.com/FasterXML/jackson-modules-java8/… for the issue .. maybe this is the one? github.com/FasterXML/jackson-modules-java8/issues/70Che
Workaround 1 works for me. And you should take care of the order of the registered modules.Albi
The issue further goes to seconds if milliseconds and seconds both are 000 and 00 respectively. If I use 2020-02-04T08:26.00.000Z my output is 2020-02-04T08:26Z. Any workaround for this?Annabelannabela
V
3

If you are trying to do this in Spring Boot and want to use @Gustavo's answer.

import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public Module javaTimeModule() {
        JavaTimeModule module = new JavaTimeModule();
        module.addSerializer(new InstantSerializerWithMilliSecondPrecision());
        return module;
    }

}
Vernettaverneuil answered 16/8, 2021 at 18:26 Comment(0)
F
1

None of two workarounds mentioned by Sean Carroll works me. I end up with writing my own serializer for Instant.

final ObjectMapper mapper = new ObjectMapper();
final JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(Instant.class, new KeepMillisecondInstantSerializer());
mapper.registerModule(javaTimeModule);

public class KeepMillisecondInstantSerializer extends JsonSerializer<Instant> {

    private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
            .withZone(ZoneId.of("UTC"));

    @Override
    public void serialize(final Instant instant, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) throws IOException {
        final String serializedInstant = dateTimeFormatter.format(instant);
        jsonGenerator.writeString(serializedInstant);
    }
}

I guess Jackson use Instant.toString() method to serialize Instant objects by default. I also find some discussions about Instant.toString() method on StackOverflow.

Fabron answered 5/9, 2018 at 18:36 Comment(0)
P
0

Rather than fixing the bug of Jackson library, following could be a quick work around: Create a string variable in the POJO class where you have Timestamp variable:

private Timestamp createTimeStamp;

private String stringCreateTimeStamp;   

capture timestamp value as a string:

listOfPojo.forEach(pojo-> {
            pojo.setStringCreateTimeStamp(request.getcreateTimeStamp().toString());
        });

Refer https://www.baeldung.com/java-string-to-timestamp for conversions

Porbeagle answered 27/5, 2020 at 23:58 Comment(0)
C
0

Solve it by using custom serializers for LocalDateTime and ZonedDateTime classes.

My solution works for me because I use only these two classes in API responses to represent date and time! I don't use Instant or Date so pay attention on it.

@Configuration
class JacksonConfig {

@Bean
fun objectMapper(): ObjectMapper {
    val mapper = ObjectMapper()
    val javaTimeModule = JavaTimeModule().apply {
        addSerializer(LocalDateTime::class.java, KeepMillisecondLocalDateTimeSerializer())
        addSerializer(ZonedDateTime::class.java, KeepMillisecondZonedDateTimeSerializer())
    }
    mapper.registerModule(javaTimeModule)
    return mapper
}

class KeepMillisecondZonedDateTimeSerializer : JsonSerializer<ZonedDateTime>() {
    private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")

    @Throws(IOException::class)
    override fun serialize(
        value: ZonedDateTime,
        jsonGenerator: JsonGenerator,
        serializerProvider: SerializerProvider?
    ) {
        jsonGenerator.writeString(formatter.format(value))
    }
}

class KeepMillisecondLocalDateTimeSerializer : JsonSerializer<LocalDateTime>() {
    private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")

    @Throws(IOException::class)
    override fun serialize(
        value: LocalDateTime,
        jsonGenerator: JsonGenerator,
        serializerProvider: SerializerProvider?
    ) {
        jsonGenerator.writeString(formatter.format(value))
    }
}
}
Clactonian answered 11/9, 2020 at 14:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.