使用JacksonMapper反序列化Java 8 LocalDateTime

62

我在SO上读到了几个关于java.time.LocalDateTime和JSON属性之间序列化和反序列化的问题和答案,但是我似乎无法让它正常工作。

我已经成功配置了我的Spring Boot应用程序,使其以我所需的格式(YYY-MM-dd HH:mm)返回日期,但我无法接受以此格式在JSON中传递的值。

这是我迄今为止所做的所有事情:

添加了jsr310的Maven依赖:

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

在我的主类中指定了jsr310:

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

禁用在application.properties中的时间戳序列化:

spring.jackson.serialization.write_dates_as_timestamps=false

这是我的日期时间实体映射:

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

在我的数据库中,我将这个日期以以下格式存储为时间戳:2016-12-01T23:00:00+00:00

当我通过控制器访问此实体时,它返回具有正确开始日期格式的JSON。但是,当我尝试以YYYY-MM-dd HH:mm格式进行发布和反序列化时,我会收到以下异常:

{
  "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"
}

我知道关于这个话题有很多答案,但是遵循它们并尝试了几个小时后都没有帮助我找出我做错了什么,所以如果有人能指出我漏掉了什么,我将非常感激。谢谢对此的任何建议!

编辑:这些都是涉及到这个过程的所有类:

Repository:

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

控制器:

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

我的JSON请求负载:

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

事件:

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

请问您能否分享剩余的跟踪信息,以便我们可以验证哪些类涉及解析该值?此外,DateTimeFormat注释似乎不太合适。 - Ivan
@Ivan,我已经在我的问题中添加了所有细节并更新了跟踪。 - Smajl
你有做什么特别的事情来让它输出那样的错误信息吗?我有一个类似的问题,只是我的本地日期时间在提交请求时变成了null,尽管我可以进行Get并且格式化也很好。 - Steve
9个回答

77

您提供的日期时间格式不是ISO本地日期时间格式。

请更改为:

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

并以格式“2011-12-03T10:15:30”传递日期字符串。

但是,如果您仍想传递自定义格式,则只需指定正确的格式化程序即可。

更改为

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

我认为你的问题是@DateTimeFormat没有任何作用。因为Jackson正在进行反序列化,它不知道任何关于Spring注解的信息,我没有看到Spring在反序列化上下文中扫描此注解。

或者,您可以尝试在注册Java时间模块时设置格式化程序。

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

这是一个正常工作的反序列化器测试案例。也许可以考虑完全取消DateTimeFormat注释。

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

3
我有一个错误,无法从DateTimeFormatter转换为DateTimeFormat.ISO。 - Smajl
2
我现在无法测试。你能试试吗 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") - s7vr
21
DateTimeFormatter.ISO_LOCAL_DATE 抛出编译时错误:属性值必须是常量 - ankit
3
就像@ankit所说,这个无法编译。属性值必须是常量 - Spenhouet
1
这部分是错误的:@DateTimeFormat(iso = DateTimeFormatter.ISO_LOCAL_DATE_TIME) - Felipe Desiderati
显示剩余13条评论

32

您在该行中使用了错误的年份大小写:

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

应该是:

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

有了这个改变,一切都按预期运行。


13
这对我有用:

这个方法适合我:

 @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ", shape = JsonFormat.Shape.STRING)
 private LocalDateTime startDate;

是的..我正在寻找这个东西。对于问题中提到的确切日期时间值"2016-10-30T14:22:25.285+0000",这是对我有效的解决方案。 - Anupam Jain
谢谢,这个修复了我的解析问题,从2022-02-21T17:57:53.049+0000开始。但是,shape = JsonFormat.Shape.STRING是什么意思? - Xelian
我不得不对模式进行轻微更改(使用 将 Z 包围起来):@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")` - Alexandre Alves

10

你的代码存在两个问题:

1. 使用了错误的类型

LocalDateTime 不支持时区。下面是java.time 类型的概述,你可以看到匹配你日期时间字符串 2016-12-01T23:00:00+00:00 的类型是 OffsetDateTime,因为它有一个偏移量为 +00:00

enter image description here

请将声明更改为以下内容:

private OffsetDateTime startDate;

2. 使用错误的格式

格式存在两个问题:

  • 你需要使用y(年份)而不是Y(基于周的年份)。查看此讨论了解更多相关信息。事实上,我建议你使用u(年份)而不是y(年份)。查看此答案以获取更多详细信息。
  • 你需要使用XXXZZZZZ表示偏移量,因此你的格式应该为uuuu-MM-dd'T'HH:m:ssXXX

查看DateTimeFormatter文档页面了解这些符号/格式的更多详细信息。

演示:

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        String strDateTime = "2019-10-21T13:00:00+02:00";
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:m:ssXXX");
        OffsetDateTime odt = OffsetDateTime.parse(strDateTime, dtf);
        System.out.println(odt);
    }
}

输出:

2019-10-21T13:00+02:00

了解有关现代日期时间API的更多信息,请参阅Trail: Date Time


9

更新:

更改为:

@Column(name = "start_date")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm", iso = ISO.DATE_TIME)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime startDate;

JSON请求:

{
 "startDate":"2019-04-02 11:45"
}

4
你可以实现自己的JsonSerializer。
参见:
在你的bean属性中。
@JsonProperty("start_date")
@JsonFormat("YYYY-MM-dd HH:mm")
@JsonSerialize(using = DateSerializer.class)
private Date startDate;

这样实现您自定义的类。
public class DateSerializer extends JsonSerializer<Date> implements ContextualSerializer<Date> {

    private final String format;

    private DateSerializer(final String format) {
        this.format = format;
    }

    public DateSerializer() {
        this.format = null;
    }

    @Override
    public void serialize(final Date value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
        jgen.writeString(new SimpleDateFormat(format).format(value));
    }

    @Override
    public JsonSerializer<Date> createContextual(final SerializationConfig serializationConfig, final BeanProperty beanProperty) throws JsonMappingException {
        final AnnotatedElement annotated = beanProperty.getMember().getAnnotated();
        return new DateSerializer(annotated.getAnnotation(JsonFormat.class).value());
    }

}

在您发布结果后,请尝试此方法。


请回答我的问题:https://stackoverflow.com/questions/52129200/resolverstyle-strict-is-not-working-in-datetimeformatiso-datetimeformat-iso?noredirect=1#comment91412598_52129200 - ankit

3

这对我起了作用:

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;

@Column(name="end_date", nullable = false)
@DateTimeFormat(iso = ISO.DATE_TIME)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime endDate;

0

这个适用于这个JSON { "pickupDate":"2014-01-01T00:00:00" }

@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime pickupDate;

0

对我来说它有效。

@Column(name = "lastUpdateTime")
@DateTimeFormat(iso = ISO.DATE_TIME,pattern = "yyyy-MM-dd HH:mm:ss" )   
@JsonIgnore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接