Spring deserializes LocalDate in @RequestBody differently than in @RequestParam - why, and can they be the same?

QUESTION: Spring apparently uses different deserialization methods for LocalDate depending on whether it is @RequestBody in @RequestBody or @ReqestParam request - is this correct, and if so, is there any way to configure them to be the same throughout the application?

BACKGROUND: In my @RestController , I have two methods: one GET and one POST. GET expects a request parameter ("date") that is of type LocalDate ; POST expects a JSON object in which one key ("date") is of type LocalDate . Their signatures are similar to the following:

 @RequestMapping(value = "/entity", method = RequestMethod.GET) public EntityResponse get( Principal principal, @RequestParam(name = "date", required = false) LocalDate date) @RequestMapping(value = "/entity", method = RequestMethod.POST) public EntityResponse post( Principal principal, @RequestBody EntityPost entityPost) public class EntityPost { public LocalDate date; } 

I configured my ObjectMapper as follows:

 @Bean public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.enable(SerializationFeature.INDENT_OUTPUT); objectMapper.registerModule(new JavaTimeModule()); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return objectMapper; } 

This ensures that the system accepts LocalDate in the format yyyy-MM-dd and deserializes it as expected, at least when it is part of @RequestBody . So if for post

is the request body:
 { "date": 2017-01-01 } 

The system deserializes the request body in EntityPost as expected.

However, this configuration does not apply to @RequestParam deserialization. As a result, this fails:

 // fail! /entity?date=2017-01-01 

Instead, the system expects the format to be MM / dd / yy. As a result, it succeeds:

 // success! /entity?date=01/01/17 

I know I can change this based on parameter by parameter using @DateTimeFormat annotation. I know that if I change the signature of the GET method as follows, it will take the first format:

 @RequestMapping(value = "/entity", method = RequestMethod.GET) public EntityResponse get( Principal principal, @RequestParam(name = "date", required = false) @DateTimeFormat(iso=DateTimeFormat.ISO.DATE) LocalDate date) 

However, I would prefer that I do not have to include an annotation for every use of LocalDate . Is there a way to set this globally so that the system deserializes every @RequestParam type LocalDate in the same way?

For reference:

I am using Spring 4.3.2.RELEASE

I am using Jackson 2.6.5

+3
source share
2 answers

Per @Andreas in the comments, Spring Framework uses Jackson to deserialize @RequestBody , but Spring itself deserializes @RequestParam . This is the source of the difference between the two.

This answer shows how to use @ControllerAdvice and @InitBinder to configure @RequestParam deserialization. The code that I ultimately used follows:

 import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.InitBinder; import java.beans.PropertyEditorSupport; import java.text.Format; import java.time.*; import java.time.format.DateTimeFormatter; import java.util.function.Function; @ControllerAdvice public class ControllerAdviceInitBinder { private static class Editor<T> extends PropertyEditorSupport { private final Function<String, T> parser; private final Format format; public Editor(Function<String, T> parser, Format format) { this.parser = parser; this.format = format; } public void setAsText(String text) { setValue(this.parser.apply(text)); } public String getAsText() { return format.format((T) getValue()); } } @InitBinder public void initBinder(WebDataBinder webDataBinder) { webDataBinder.registerCustomEditor( Instant.class, new Editor<>( Instant::parse, DateTimeFormatter.ISO_INSTANT.toFormat())); webDataBinder.registerCustomEditor( LocalDate.class, new Editor<>( text -> LocalDate.parse(text, DateTimeFormatter.ISO_LOCAL_DATE), DateTimeFormatter.ISO_LOCAL_DATE.toFormat())); webDataBinder.registerCustomEditor( LocalDateTime.class, new Editor<>( text -> LocalDateTime.parse(text, DateTimeFormatter.ISO_LOCAL_DATE_TIME), DateTimeFormatter.ISO_LOCAL_DATE_TIME.toFormat())); webDataBinder.registerCustomEditor( LocalTime.class, new Editor<>( text -> LocalTime.parse(text, DateTimeFormatter.ISO_LOCAL_TIME), DateTimeFormatter.ISO_LOCAL_TIME.toFormat())); webDataBinder.registerCustomEditor( OffsetDateTime.class, new Editor<>( text -> OffsetDateTime.parse(text, DateTimeFormatter.ISO_OFFSET_DATE_TIME), DateTimeFormatter.ISO_OFFSET_DATE_TIME.toFormat())); webDataBinder.registerCustomEditor( OffsetTime.class, new Editor<>( text -> OffsetTime.parse(text, DateTimeFormatter.ISO_OFFSET_TIME), DateTimeFormatter.ISO_OFFSET_TIME.toFormat())); webDataBinder.registerCustomEditor( ZonedDateTime.class, new Editor<>( text -> ZonedDateTime.parse(text, DateTimeFormatter.ISO_ZONED_DATE_TIME), DateTimeFormatter.ISO_ZONED_DATE_TIME.toFormat())); } } 
0
source

Create a Formatter for LocalDate:

 public class LocalDateFormatter implements Formatter<LocalDate> { @Override public LocalDate parse(String text, Locale locale) throws ParseException { return LocalDate.parse(text, DateTimeFormatter.ISO_DATE); } @Override public String print(LocalDate object, Locale locale) { return DateTimeFormatter.ISO_DATE.format(object); } } 

Spring 5 +: Register the formatter: Add WebMvcConfigurer to @Configuration and override addFormatters :

 @Override public void addFormatters(FormatterRegistry registry) { registry.addFormatter(new LocalDateFormatter()); } 

Spring Download: Define @Primary @Bean to override the default formatting:

 @Bean @Primary public Formatter<LocalDate> localDateFormatter() { return new LocalDateFormatter(); } 
+1
source

Source: https://habr.com/ru/post/1270440/


All Articles