Jackson OffsetDateTime 序列化 Z 而不是 +00:00 时区?

Posted

技术标签:

【中文标题】Jackson OffsetDateTime 序列化 Z 而不是 +00:00 时区?【英文标题】:Jackson OffsetDateTime serialization Z instead of +00:00 timezone? 【发布时间】:2020-06-12 13:41:37 【问题描述】:

我正在使用带有以下 ObjectMapper 的 Spring Boot:

@Bean
public ObjectMapper objectMapper()

    final ObjectMapper mapper = new ObjectMapper();

    mapper.enable(SerializationFeature.INDENT_OUTPUT);

    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);  
    mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true)); // Makes no difference to output

    mapper.findAndRegisterModules();

    return mapper;

当 OffsetDateTimes 被序列化并在响应中返回时,它们的格式如下:

"2020-02-28T12:28:29.01Z"
"2020-02-28T12:36:21.885Z"

我本来希望最后的时区信息看起来像这样:

"2020-02-28T10:41:25.287+00:00"

我在这里有什么遗漏或做错了吗,或者无论如何我可以将时区信息序列化为+00:00 格式而不是885Z 格式?

非常感谢!

【问题讨论】:

它确实包括了时区,只是不是你所期望的。 'Z' 代表祖鲁时间,类似于 +00:00 【参考方案1】:

使用预构建格式或向DateTimeFormatter 提供自定义模式有多种可能性。

看看这些(非常简单的)例子:

public static void main(String[] arguments) 
    Instant now = Instant.now();
    
    ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(now, ZoneId.of("UTC"));
    OffsetDateTime offsetDateTime = OffsetDateTime.ofInstant(now, ZoneId.of("UTC"));
    
    System.out.println(zonedDateTime.toString());
    System.out.println(offsetDateTime.toString());
    System.out.println(zonedDateTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
    System.out.println(offsetDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
    System.out.println(zonedDateTime.format(
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"))
    );
    System.out.println(offsetDateTime.format(
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssxxx")
        )
    );

我认为您所期望的是最后一个,以xxx 结尾的模式,这导致偏移量始终以HH:mm 的形式显示,代码示例的输出是:

2020-02-28T12:49:02.388Z[UTC]
2020-02-28T12:49:02.388Z
2020-02-28T12:49:02.388Z[UTC]
2020-02-28T12:49:02.388Z
2020-02-28T12:49:02+0000
2020-02-28T12:49:02+00:00

【讨论】:

【参考方案2】:

新的 Java 8 Time API 提供了一个DateTimeFormatter,您可以在其中将格式的结尾设置为一个或多个xX。根据api描述:

偏移 X 和 x:这会根据模式字母的数量来格式化偏移。一个字母仅输出小时,例如“+01”,除非分钟不为零,在这种情况下也会输出分钟,例如“+0130”。两个字母输出小时和分钟,不带冒号,如'+0130'。三个字母输出小时和分钟,带有冒号,例如'+01:30'。四个字母输出小时和分钟以及可选的秒,不带冒号,例如'+013015'。五个字母输出小时和分钟以及可选的秒,带有冒号,例如'+01:30:15'。六个或更多字母会引发 IllegalArgumentException。当要输出的偏移量为零时,模式字母“X”(大写)将输出“Z”,而模式字母“x”(小写)将输出“+00”、“+0000”或“+00” :00'。

因此,在您的情况下,您的格式字符串应该以 xxx 结尾,对于 +1:30,例如"yyyy-MM-dd'T'HH:mm:ss.SSSxxx"SSS 总是给出毫秒数。

要将此DateTimeFormatter 与 Jackson 一起使用,您需要定义一个自定义序列化程序

public class DefaultZonedDateTimeSerializer extends JsonSerializer<ZonedDateTime> 


  private static final DateTimeFormatter ISO_8601_FORMATTER = DateTimeFormatter
        .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
        .withZone(ZoneId.of("UTC"));

  @Override
  public void serialize(ZonedDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException 
    if (value == null) 
        throw new IOException("ZonedDateTime argument is null.");
    

    gen.writeString(ISO_8601_FORMATTER.format(value));

并用

注释你的bean中的各个字段
@JsonSerialize(using = DefaultZonedDateTimeSerializer.class)
private ZonedDateTime someTimeProperty;

或者您需要从 DateTimeFormatter 转换为 DateFormat(较旧,但由 Jackson 使用),如下所述:Using DateTimeFormatter with ObjectMapper

【讨论】:

【参考方案3】:

以下步骤解决了这个问题(取自https://***.com/a/41893238/12177456),还要感谢@Ralf Wagner 和@deHaar:

<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
  <version>2.6.5</version>
</dependency>
public class OffsetDateTimeSerializer extends JsonSerializer<OffsetDateTime>

    private static final DateTimeFormatter ISO_8601_FORMATTER = DateTimeFormatter
        .ofPattern("yyyy-MM-dd'T'HH:mm:ssxxx")
        .withZone(ZoneId.of("UTC"));

    @Override
    public void serialize(OffsetDateTime value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException
    
        if (value == null) 
            throw new IOException("OffsetDateTime argument is null.");
        

        jsonGenerator.writeString(ISO_8601_FORMATTER.format(value));
    

@Bean
public ObjectMapper objectMapper()


    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
    objectMapper.registerModule(new JavaTimeModule());
    SimpleModule simpleModule = new SimpleModule();

    simpleModule.addSerializer(OffsetDateTime.class, new OffsetDateTimeSerializer());
    objectMapper.registerModule(simpleModule);

    return objectMapper;

【讨论】:

看起来很酷,但没有帮助:2019-11-30T10:30:00.123+03:00 仍然转换为 2019-11-30T07:30:00.123Z :( new ObjectMapper(),registerModule(new JavaTimeModule()).configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false).configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false) 实际上解决了我之前评论中的问题。【参考方案4】:

如果您使用的是 JodaDateTime,请尝试以下操作:

    @Bean
    public ObjectMapper getObjectMapper() 
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JodaModule());
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        return objectMapper;
    

【讨论】:

以上是关于Jackson OffsetDateTime 序列化 Z 而不是 +00:00 时区?的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring REST Controller 中对 DateTime 使用 Jackson 反序列化

为啥 OffsetDateTime 序列化/反序列化结果有差异?

使用 Spring Boot webclient 反序列化 OffsetDateTime

我无法将 '2017-04-04T08:04+0000' 解析为 OffsetDateTime

SpringBoot RestTemplate 忽略 spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false

json之jackson序列化反序列化探究(二)