Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserialize Java 8 LocalDateTime with JacksonMapper

I have read several questions with answers here in SO concerning serialization and deserialization between java.time.LocalDateTime and JSON property but I can't seem to get it working.

I have managed to configure my Spring Boot Application to return the dates in the format I desire (YYY-MM-dd HH:mm) but I have problems accepting values in this format in JSON.

These are all the things I have done so far:

Added maven dependency for jsr310:

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

Specified jsr310 in my main class:

@EntityScan(basePackageClasses = { App.class, Jsr310JpaConverters.class }) 

Disabled serialization as timestamps in application.properties:

spring.jackson.serialization.write_dates_as_timestamps=false 

And this is my entity mapping for datetime:

@Column(name = "start_date") @DateTimeFormat(iso = DateTimeFormat.ISO.TIME) @JsonFormat(pattern = "YYYY-MM-dd HH:mm") private LocalDateTime startDate; 

In my database, I store this date as TIMESTAMP in the following format: 2016-12-01T23:00:00+00:00.

If I access this entity via my controller, it returns the JSON with correct startDate format. When I try to post it and deserialize it though, using YYYY-MM-dd HH:mm format, I get the following exception:

{   "timestamp": "2016-10-30T14:22:25.285+0000",   "status": 400,   "error": "Bad Request",   "exception": "org.springframework.http.converter.HttpMessageNotReadableException",   "message": "Could not read document: Can not deserialize value of type java.time.LocalDateTime from String \"2017-01-01 20:00\": Text '2017-01-01 20:00' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {MonthOfYear=1, WeekBasedYear[WeekFields[SUNDAY,1]]=2017, DayOfMonth=1},ISO resolved to 20:00 of type java.time.format.Parsed\n at [Source: java.io.PushbackInputStream@679a734d; line: 6, column: 16] (through reference chain: com.gigsterous.api.model.Event[\"startDate\"]); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type java.time.LocalDateTime from String \"2017-01-01 20:00\": Text '2017-01-01 20:00' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {MonthOfYear=1, WeekBasedYear[WeekFields[SUNDAY,1]]=2017, DayOfMonth=1},ISO resolved to 20:00 of type java.time.format.Parsed\n at [Source: java.io.PushbackInputStream@679a734d; line: 6, column: 16] (through reference chain: com.gigsterous.api.model.Event[\"startDate\"])",   "path": "/api/events" } 

I know that there are many answers concerning this topic but following them and trying for couple of hours did not help me to figure out what am I doing wrong so I would be glad if someone could point out to me what am I missing. Thanks for any input on this!

EDIT: These are all the classes involved in the process:

Repository:

@Repository public interface EventRepository extends PagingAndSortingRepository<Event, Long> { } 

Controller:

@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<Event> createEvent(@RequestBody Event event) {         return new ResponseEntity<>(eventRepo.save(event), HttpStatus.CREATED); } 

My JSON request payalod:

{   "name": "Test",   "startDate": "2017-01-01 20:00" } 

Event:

@Entity @Table(name = "events") @Getter @Setter public class Event {      @Id     @GeneratedValue(strategy = GenerationType.AUTO)     @Column(name = "event_id")     private long id;      @Column(name = "name")     private String name;      @Column(name = "start_date")     @DateTimeFormat(iso = DateTimeFormat.ISO.TIME)     @JsonFormat(pattern = "YYYY-MM-dd HH:mm")     private LocalDateTime startDate; } 
like image 371
Smajl Avatar asked Oct 30 '16 10:10

Smajl


People also ask

How do I deserialize a date in a string?

This is how i'm deserializing the date: SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); getObjectMapper(). getDeserializationConfig(). setDateFormat(dateFormat);

Is LocalDateTime immutable Java?

LocalDateTime is an immutable date-time object that represents a date-time, often viewed as year-month-day-hour-minute-second. Other date and time fields, such as day-of-year, day-of-week and week-of-year, can also be accessed.

How can we create LocalDateTime object with current date and time?

The easiest way to create an instance of the LocalDateTime class is by using the factory method of(), which accepts year, month, day, hour, minute, and second to create an instance of this class. A shortcut to create an instance of this class is by using atDate() and atTime() method of LocalDate and LocalTime class.


2 Answers

The date time you're passing is not an ISO local date time format.

Change to

@Column(name = "start_date") @DateTimeFormat(iso = DateTimeFormatter.ISO_LOCAL_DATE_TIME) @JsonFormat(pattern = "YYYY-MM-dd HH:mm") private LocalDateTime startDate; 

and pass the date string in the format '2011-12-03T10:15:30'.

But if you still want to pass your custom format, you just have to specify the right formatter.

Change to

@Column(name = "start_date") @DateTimeFormat(iso = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) @JsonFormat(pattern = "YYYY-MM-dd HH:mm") private LocalDateTime startDate; 

I think your problem is the @DateTimeFormat has no effect at all. As the Jackson is doing the deserialization and it doesn't know anything about spring annotation, and I don't see spring scanning this annotation in the deserialization context.

Alternatively, you can try setting the formatter while registering the Java time module.

LocalDateTimeDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer); 

Here is the test case with the deseralizer which works fine. Maybe try to get rid of that DateTimeFormat annotation altogether.

@RunWith(JUnit4.class) public class JacksonLocalDateTimeTest {      private ObjectMapper objectMapper;      @Before     public void init() {         JavaTimeModule module = new JavaTimeModule();         LocalDateTimeDeserializer localDateTimeDeserializer =  new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));         module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);         objectMapper = Jackson2ObjectMapperBuilder.json()                 .modules(module)                 .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)                 .build();     }      @Test     public void test() throws IOException {         final String json = "{ \"date\": \"2016-11-08 12:00\" }";         final JsonType instance = objectMapper.readValue(json, JsonType.class);          assertEquals(LocalDateTime.parse("2016-11-08 12:00",DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") ), instance.getDate());     } }   class JsonType {     private LocalDateTime date;      public LocalDateTime getDate() {         return date;     }      public void setDate(LocalDateTime date) {         this.date = date;     } } 
like image 120
s7vr Avatar answered Sep 28 '22 02:09

s7vr


You used wrong letter case for year in line:

@JsonFormat(pattern = "YYYY-MM-dd HH:mm") 

Should be:

@JsonFormat(pattern = "yyyy-MM-dd HH:mm") 

With this change everything is working as expected.

like image 27
Maciej Walkowiak Avatar answered Sep 28 '22 00:09

Maciej Walkowiak