Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.8k views
in Technique[技术] by (71.8m points)

json - 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
 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
 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;
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...