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?
André Gasser
  • 1,065
  • 2
  • 14
  • 34

6 Answers6

9

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

Gustavo
  • 91
  • 1
  • 5
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

Max Peng
  • 2,879
  • 1
  • 26
  • 43
Sean Carroll
  • 2,651
  • 2
  • 24
  • 21
  • 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. – André Gasser Nov 27 '17 at 13:26
  • I tried Jackson 2.9.5. and this issue is still not fixed. But your 1.st workaround works, thank you – vanillaSugar Apr 18 '18 at 11:48
  • I was looking here https://github.com/FasterXML/jackson-modules-java8/issues?q=is%3Aissue+is%3Aclosed for the issue .. maybe this is the one? https://github.com/FasterXML/jackson-modules-java8/issues/70 – vanillaSugar Apr 18 '18 at 13:09
  • Workaround 1 works for me. And you should take care of the order of the registered modules. – Max Peng Apr 09 '19 at 08:48
  • 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? – Bhupesh_decoder Nov 12 '20 at 16:30
2

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;
    }

}
Hermann Steidel
  • 1,000
  • 10
  • 18
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.

Sher10ck
  • 341
  • 1
  • 4
  • 7
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

Jaya
  • 13
  • 5
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))
    }
}
}
Ivan Rudnev
  • 380
  • 7
  • 12