0

I'm using spring boot 2.3.4.RELEASE and when trying to convert a DTO containing an Instant attribute to JSON format, the jackson ObjectMapper keeps converting it to timestamp format, even with write-dates-as-timestamps option turned off.

pom.xml

<parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
     <version>2.3.4.RELEASE</version>
     <relativePath/> <!-- lookup parent from repository -->
</parent> 

....

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

application.properties

spring.jackson.serialization.write-dates-as-timestamps = false

DTO:

@Data
public class MyDTO {

    Long id;

    @NotNull
    String number;

    @NotNull
    Integer year;

    @NotNull
    VesselContractStatusEnum status;

    Instant statusDate;
}

Rest Controller response:

{
    "id": 1,
    "number": "202000",
    "year": 2020,
    "status": "Open",
    "statusDate": 1602298800
 }

Following recommendations found here in StackOverflow, I tried to override the ObjectMapper using the follwoing approach, but it didn't work.

@Bean
@Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {

    ObjectMapper objectMapper = builder.build();

    objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

    JavaTimeModule javaTimeModule = new JavaTimeModule();
    objectMapper.registerModule(javaTimeModule);
    objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"));

    return objectMapper;
}
dinhokz
  • 895
  • 15
  • 36
  • Disabling `SerializationFeature.WRITE_DATES_AS_TIMESTAMPS` should be enough but do that by providing global `Jackson` properties. Take a look at [Customize the Jackson ObjectMapper](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-customize-the-jackson-objectmapper). `WRITE_DATES_AS_TIMESTAMPS` should be disabled by default but you need to check it in your app. If `JavaTimeModule` is on classpath it should be found and recognised by `Jackson2ObjectMapperBuilder` and you don't have to register it manually. – Michał Ziober Oct 28 '20 at 00:04

3 Answers3

1

Instant.toString() returns the ISO8601 formatted string (for what its worth). Perhaps providing a getter for the Json field would help?

Rob Evans
  • 2,822
  • 1
  • 9
  • 15
  • I think it should work, but my point is that I don't want to change all my DTOs and rely on it. I want to change my spring serialization configuration "globally". – dinhokz Oct 27 '20 at 13:30
  • 1
    I have done something like provide an @Bean ObjectMapper that has `objectMapper.registerModule(new JavaTimeModule())` - I know this is what you mean - I don't know if this is how you'd do it in your use-case but perhaps this gives you an idea of what to search for? Like this : https://stackoverflow.com/questions/29571587/deserializing-localdatetime-with-jackson-jsr310-module – Rob Evans Oct 27 '20 at 13:34
1

You have to write a Custom Serializer and De Serializer classes as below:

Serializer Class:

public class InstantSerializer extends JsonSerializer<Instant> {

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

    @Override
    public void serialize(Instant instant, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
        String str = dateTimeFormatter.format(instant);

        jsonGenerator.writeString(str);
    }
}

Deserializer Class:

public class InstantDeserializer extends JsonDeserializer<Instant> {

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

    @Override
    public Instant deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        return Instant.from(dateTimeFormatter.parse(jsonParser.getText()));
    }
}

Then add the below annotations on top of the Instant variable in your DTO class as below:

@JsonDeserialize(using = InstantDeserializer.class)
@JsonSerialize(using = InstantSerializer.class)
Instant statusDate;
Sriram
  • 155
  • 12
  • I would prefer to configure it in the ObjectMapper itself instead of including 2 annotations on every date in the system. Do you think this is possible? I found some articles that explains what I did works, perhaps is a spring boot version mistmatch. – dinhokz Oct 27 '20 at 13:26
1

Do you have annotation

@EnableWebMvc

anywhere?

After having tried everything without success, I got it to work by removing the @EnableWebMvc annotation I had on the main class (annotated with @SpringBootApplication).

I think @EnableWebMvc takes over all MVC configuration control.
If you really need @EnableWebMvc, then you have to register the serializer / deserializer by hand. Eg. by adding something like this to the main class (annotated with @SpringBootApplication):

  @Override
  public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
  {
    SimpleModule m = new SimpleModule();
    m.addSerializer(Instant.class, new MyCustomJsonSerializer());
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder().modules(m);
    converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
  }
balieu
  • 139
  • 1
  • 5