【问题标题】:Jackson serializes a ZonedDateTime wrongly in Spring BootJackson 在 Spring Boot 中错误地序列化了 ZonedDateTime
【发布时间】:2016-12-29 09:56:55
【问题描述】:

我有一个带有 Spring Boot 和 Jetty 的简单应用程序。我有一个简单的控制器,它返回一个具有 Java 8 ZonedDateTime 的对象:

public class Device {
  // ...
  private ZonedDateTime lastUpdated;

  public Device(String id, ZonedDateTime lastUpdated, int course, double latitude, double longitude) {
    // ...
    this.lastUpdated = lastUpdated;
    // ...
  }

  public ZonedDateTime getLastUpdated() {
    return lastUpdated;
  }
}

在我的RestController 中,我只有:

@RequestMapping("/devices/")
public @ResponseBody List<Device> index() {
  List<Device> devices = new ArrayList<>();
  devices.add(new Device("321421521", ZonedDateTime.now(), 0, 39.89011333, 24.438176666));

  return devices;
}

我原以为 ZonedDateTime 会根据 ISO 格式进行格式化,但我却得到了类的整个 JSON 转储,如下所示:

"lastUpdated":{"offset":{"totalSeconds":7200,"id":"+02:00","rules":{"fixedOffset":true,"transitionRules":[],"transitions":[]}},"zone":{"id":"Europe/Berlin","rules":{"fixedOffset":false,"transitionRules":[{"month":"MARCH","timeDefinition":"UTC","standardOffset":{"totalSeconds":3600,"id":"+01:00","rules":{"fixedOffset":true,"transitionRules":[],"transitions":[]}},"offsetBefore":{"totalSeconds":3600,"id":"+01:00","rules":{"fixedOffset":true,"transitionRules":[],"transitions":[]}},"offsetAfter":{"totalSeconds":7200,"id":"+02:00", ...

我只有一个spring-boot-starter-web 应用程序,使用spring-boot-starter-jetty 并排除spring-boot-starter-tomcat

为什么 Jackson 在 Spring Boot 中会这样?

** 更新 **

对于那些寻找完整的分步指南如何解决这个问题的人,我在提出问题后发现了这一点: http://lewandowski.io/2016/02/formatting-java-time-with-spring-boot-using-json/

【问题讨论】:

  • 如果你真的想让事情变得简单而不是让下一个阅读你的代码的人绊倒,只需将你的 List 转换为 List> 自己然后放您想要的字段中格式正确的字符串。这样你就不必熟悉杰克逊不断变化的魔法工作。我知道如果 Device 有很多字段,这可能不是一个选项,但只是想浮动选项:)。
  • 好吧,如果你使用 Spring Boot,一半的事情是神奇的,如果配置错误,可能会让你绊倒。自从我发布此问题以来,JSR310 配置已成为您必须为 Java 8 Time 支持所做的“标准”样板。也许将来 Jackson 库会默认这样做。

标签: java json spring-boot jackson zoneddatetime


【解决方案1】:

有一个图书馆jackson-datatype-jsr310。试试看。

这个库涵盖了新的日期时间 API,并且还包括 ZonedDateTime 的序列化程序。

您只需添加JavaTimeModule:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());

更新

要将日期时间转换为ISO-8601 字符串,您应该禁用WRITE_DATES_AS_TIMESTAMPS 功能。您可以通过覆盖 ObjectMapper bean 或使用 application properties 轻松完成:

spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false

【讨论】:

  • 谢谢。于是我添加了jackson-datatype-jsr310版本2.8.1的依赖,效果就是时间戳变成了双"lastUpdated":1471893818.177000000。在 Spring Boot 中,我无法直接访问它正在使用的 ObjectMapper,知道我在哪里告诉它使用 JavaTimeModule? (我对 Spring Boot 有点陌生)
  • @jbx 检查这个答案 - stackoverflow.com/questions/7854030/… 您还需要尝试关闭 WRITE_DATES_AS_TIMESTAMPS 功能将日期时间转换为 ISO-8601
  • 我只是想告诉你我找到了答案。是的,它可以通过spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false 工作,无需以编程方式自定义ObjectMapper。由于我的问题专门针对 Spring boot,您能否编辑您的答案以添加此内容,以便我选择它作为正确答案?
  • @jbx 太棒了!很高兴你完全解决了。谢谢,我会编辑我的答案
  • 谢谢!它有帮助。
【解决方案2】:

如果您不依赖 SpringBoot 的自动配置功能 - 您没有在配置文件中提供 spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false 属性 - 或者出于任何原因手动创建 ObjectMapper 实例。您可以通过以下方式以编程方式禁用此功能:

ObjectMapper m = new ObjectMapper();
m.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

这是给杰克逊的2.8.7

【讨论】:

    【解决方案3】:

    上面已经提到了答案,但我认为它缺少一些信息。对于那些希望以多种形式解析 Java 8 时间戳(不仅仅是 ZonedDateTime)的人。您需要在 POM 中使用最新版本的 jackson-datatype-jsr310 并注册以下模块:

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new JavaTimeModule());
    objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    

    测试这段代码

    @Test
    void testSeliarization() throws IOException {
        String expectedJson = "{\"parseDate\":\"2018-12-04T18:47:38.927Z\"}";
        MyPojo pojo = new MyPojo(ZonedDateTime.parse("2018-12-04T18:47:38.927Z"));
    
        // serialization
        assertThat(objectMapper.writeValueAsString(pojo)).isEqualTo(expectedJson);
    
        // deserialization
        assertThat(objectMapper.readValue(expectedJson, MyPojo.class)).isEqualTo(pojo);
    }
    

    请注意,您可以在 Spring 或 dropwizard 中全局配置对象映射器来实现此目的。在不注册自定义(反)序列化程序的情况下,我还没有找到一种干净的方法来将其作为字段上的注释。

    【讨论】:

    • 我的只是包含一个测试,显示如何使用映射器。我知道这并不多,但我自己编写了代码来测试解决方案,所以我想我不妨添加它以防其他人想知道。
    【解决方案4】:

    对于杰克逊2.10 及以上,

    父 pom.xml

    <!-- https://github.com/FasterXML/jackson-bom -->
    <dependencyManagement>
      <dependency>
        <groupId>com.fasterxml.jackson</groupId>
        <artifactId>jackson-bom</artifactId>
        <version>2.10.3</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencyManagement>
    

    模块 pom.xml

    <!-- https://github.com/FasterXML/jackson-modules-java8 -->
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
    </dependency>
    

    JsonMapper 创建,可能在您的@Configuration 类中

    @Bean
    public JsonMapper jsonMapper() {
        return JsonMapper.builder()
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
            .addModule(new JavaTimeModule())
            .build();
    }
    

    进一步阅读:

    【讨论】:

    • 这与其他答案有何不同?
    • 不再推荐来自 Jackson 2.10 及以上ObjectMapper。这个答案是最新的,包含新的类(例如JsonMapper)和方法(例如addModule)。
    • 好的,感谢您的更新。实际上,将 spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false 属性从 application.yml 更改就足够了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-24
    • 2017-09-09
    • 2021-03-30
    • 1970-01-01
    • 2019-04-03
    • 1970-01-01
    • 2018-11-17
    相关资源
    最近更新 更多