Search code examples
springspring-bootspring-mvcjacksonspring-restcontroller

Is there a way to configure LocalDate format for serializing and deserializing in the whole spring application?


I have the following problem I hope someone can give me a hand:

Context: 3 Rest endpoints

  1. Create (register)
  2. Find (findKid)
  3. Report (listDashboardInfo)

Requirement: Use the same date format yyyyMMdd for LocalDates in the whole application

Problem: Using @DateTimeFormat(pattern = DateUtils.SHORT_DATE_PATTERN) works for register and listDashboardInfo but not for findKid

These are the relevant parts of the code:

BODY
{
    "sailDate": "20191201"
}
@PostMapping(KID_PATH)
@ResponseStatus(HttpStatus.CREATED)
public KidDTO register(@RequestBody @Valid KidDTO kid) {
    return kidService.saveKid(kid);
}


GET /kid/0001::20190901
RESPONSE
{
    "sailDate": "2019-09-01"
}
@GetMapping(KID_FIND_PATH)
public CompletableFuture<KidDTO> findKid(@PathVariable String id) {
    return kidService.findKid(id);
}

GET /kid?shipCode=AL&sailDate=20190901
@GetMapping(KID_LIST_PATH)
public CompletableFuture<Slice<DashboardDTO>> listDashboardInfo(@Valid DashboardFilter filter, Pageable pageable) {
    return kidService.listKidsWithStatistics(filter, pageable);
}



@Getter
@Setter
public class DashboardFilter {
    @NotNull
    @DateTimeFormat(pattern = DateUtils.SHORT_DATE_PATTERN)
    private LocalDate sailDate;
}

@Data
public class KidDTO {
    @NotNull
    @DateTimeFormat(pattern = DateUtils.SHORT_DATE_PATTERN)
    private LocalDate sailDate;
}

Tests I did:

From stackoverflow I also found Spring doesn't use Jackson to deserialize query params so: - I created a @ControllerAdvice with @InitBinder but the method setAsText is never called:

@ControllerAdvice
public class GlobalDateBinder {
    @InitBinder
    public void binder(WebDataBinder binder) {
        binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                LocalDate.parse(text, DateUtils.SHORT_DATE_FORMATTER);
            }
        });
    }
}
  • Also I tried with a @Bean public Formatter<LocalDate> localDateFormatter() but nothing change:
    @Bean
    public FormattingConversionService conversionService() {
        DefaultFormattingConversionService conversionService =
                new DefaultFormattingConversionService(false);

        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setDateFormatter(DateUtils.SHORT_DATE_FORMATTER);
        registrar.registerFormatters(conversionService);

        return conversionService;
    }

    @Bean
    public Formatter<LocalDate> localDateFormatter() {
        return new Formatter<LocalDate>() {
            @Override
            public LocalDate parse(String text, Locale locale) {
                return LocalDate.parse(text, DateUtils.SHORT_DATE_FORMATTER);
            }

            @Override
            public String print(LocalDate object, Locale locale) {
                return DateUtils.SHORT_DATE_FORMATTER.format(object);
            }
        };
    }

Any one has an idea of what is happening?

how to make the response of findKid be formatted?

How to configure the whole application with the same date format to works in serialization and parsing/deserializing processes?

UPDATE:

I found here https://stackoverflow.com/questions/30871255/spring-boot-localdate-field-serialization-and-deserialization that I can use @JsonFormat for rest controllers (serialize and deserialize) and @DateTimeFormat for ModelView controllers but using both, at the same time, fixed my error so I don't understand why is that behavior if I only have rest controllers. Looks like in my case @DateTimeFormat deserialize and @JsonFormat serialize, is that the expected behavior? Is there any misconfiguration?


Solution

  • you can add this bean to you configuration:

        @Bean
        public ObjectMapper objectMapper() {
            DateTimeFormatter dateFormatter; // create your date formatter
            DateTimeFormatter dateTimeFormatter; // create your date and time formatter
            ObjectMapper mapper = new ObjectMapper();
            SimpleModule localDateModule = new SimpleModule();
            localDateModule.addDeserializer(LocalDate.class,
                    new LocalDateDeserializer(formatter));
            localDateModule.addSerializer(LocalDate.class,
                    new LocalDateSerializer(formatter));
            localDateModule.addDeserializer(LocalDateTime.class,
                    new LocalDateTimeDeserializer(dateTimeFormatter));
            localDateModule.addSerializer(LocalDateTime.class,
                    new LocalDateTimeSerializer(dateTimeFormatter));
    
            mapper.registerModules(localDateModule);
            return mapper;
        }