如何用Jackson和java.time解析不同的ISO日期/时间格式?

时间:2018-03-20 17:34:36

标签: spring-boot kotlin java-time jackson2

我们的Rest API从多个外部方获取JSON输入。他们都使用" ISO-ish"格式,但时区偏移的格式略有不同。这些是我们看到的一些最常见的格式:

2018-01-01T15:56:31.410Z
2018-01-01T15:56:31.41Z
2018-01-01T15:56:31Z
2018-01-01T15:56:31+00:00
2018-01-01T15:56:31+0000
2018-01-01T15:56:31+00

我们的堆栈是带有Jackson ObjectMapper的Spring Boot 2.0。在我们的数据类中,我们经常使用类型java.time.OffsetDateTime

一些开发人员试图实现解析所有上述格式的解决方案,但都没有成功。特别是带冒号(00:00)的第四个变体似乎是不可解析的。

如果解决方案无需在模型的每个日期/时间字段上放置注释,那就太棒了。

亲爱的社区,您有解决方案吗?

3 个答案:

答案 0 :(得分:2)

另一种方法是创建自定义反序列化器。首先,您注释相应的字段:

df[['a','b','c']].plot(ax = ax2, color=colors)

然后创建反序列化器。它使用java.time.format.DateTimeFormatterBuilder,使用许多可选部分来处理所有不同类型的偏移:

@JsonDeserialize(using = OffsetDateTimeDeserializer.class)
private OffsetDateTime date;

我还使用了内置常量DateTimeFormatter.ISO_LOCAL_DATE_TIME,因为它处理了可选的秒数 - 并且小数位的数量似乎也是可变的,并且这个内置格式化器已经处理好了这些细节适合你。

我使用JDK 1.8.0_144并找到了一个更短(但不多)的解决方案:

public class OffsetDateTimeDeserializer extends JsonDeserializer<OffsetDateTime> {

    private DateTimeFormatter fmt = new DateTimeFormatterBuilder()
        // date/time
        .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        // offset (hh:mm - "+00:00" when it's zero)
        .optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
        // offset (hhmm - "+0000" when it's zero)
        .optionalStart().appendOffset("+HHMM", "+0000").optionalEnd()
        // offset (hh - "+00" when it's zero)
        .optionalStart().appendOffset("+HH", "+00").optionalEnd()
        // offset (pattern "X" uses "Z" for zero offset)
        .optionalStart().appendPattern("X").optionalEnd()
        // create formatter
        .toFormatter();

    @Override
    public OffsetDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        return OffsetDateTime.parse(p.getText(), fmt);
    }
}

您可以做的另一项改进是将格式化程序更改为private DateTimeFormatter fmt = new DateTimeFormatterBuilder() // date/time .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) // offset +00:00 or Z .optionalStart().appendOffset("+HH:MM", "Z").optionalEnd() // offset +0000, +00 or Z .optionalStart().appendOffset("+HHmm", "Z").optionalEnd() // create formatter .toFormatter(); because this class is immutable and thread-safe

答案 1 :(得分:1)

这只是答案的四分之一。我既没有Kotlin也没有Jackson的经验,但我有几个我想贡献的Java解决方案。如果你能以某种方式将它们整合到一个完整的解决方案中,我会很高兴。

    String modifiedEx = ex.replaceFirst("(\\d{2})(\\d{2})$", "$1:$2");
    System.out.println(OffsetDateTime.parse(modifiedEx));

在我的Java 9(9.0.4)上,one-arg OffsetDateTime.parse解析所有示例字符串,但偏移+0000没有冒号的字符串除外。所以我的黑客是插入该冒号然后解析。以上解析所有字符串。它在Java 8中不起作用(从Java 8到Java 9有一些变化)。

在Java 8中运行的更好的解决方案(我已经测试过):

    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
            .appendPattern("[XXX][XX][X]")
            .toFormatter();
    System.out.println(OffsetDateTime.parse(ex, formatter));

模式XXXXXX分别与+00:00+0000+00匹配。我们需要按照从最长到最短的顺序尝试它们,以确保在所有情况下都解析所有文本。

答案 2 :(得分:1)

非常感谢你的所有投入!

我选择了jeedas建议的解串器,结合Ole V.V建议的格式化器(因为它更短)。

class DefensiveIsoOffsetDateTimeDeserializer : JsonDeserializer<OffsetDateTime>() {
    private val formatter = DateTimeFormatterBuilder()
        .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        .appendPattern("[XXX][XX][X]")
        .toFormatter()

    override fun deserialize(p: JsonParser, ctxt: DeserializationContext) 
      = OffsetDateTime.parse(p.text, formatter)

    override fun handledType() = OffsetDateTime::class.java
}

我还添加了一个自定义序列化程序,以确保在生成json时使用正确的格式:

class OffsetDateTimeSerializer: JsonSerializer<OffsetDateTime>() {
    override fun serialize(
        value: OffsetDateTime, 
        gen: JsonGenerator, 
        serializers: SerializerProvider
    ) = gen.writeString(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))

    override fun handledType() = OffsetDateTime::class.java
}

将所有部分放在一起,我在我的spring类路径中添加了一个@Configuraton类,使其无需对数据类进行任何注释即可使用:

@Configuration
open class JacksonConfig {

  @Bean
  open fun jacksonCustomizer() = Jackson2ObjectMapperBuilderCustomizer { 
    it.deserializers(DefensiveIsoOffsetDateTimeDeserializer())
    it.serializers(OffsetDateTimeSerializer())
  }
}