【问题标题】:How to parse different ISO date/time formats with Jackson and java.time?如何使用 Jackson 和 java.time 解析不同的 ISO 日期/时间格式?
【发布时间】:2018-08-29 15:39:11
【问题描述】:

我们的 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) 似乎无法解析。

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

尊敬的社区,您有解决方案吗?

【问题讨论】:

  • ISO 8601 有一些变化空间。我相信你所有的例子都符合。

标签: spring-boot kotlin java-time jackson2


【解决方案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())
  }
}

【讨论】:

    【解决方案2】:

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

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

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

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

    我还使用了内置常量DateTimeFormatter.ISO_LOCAL_DATE_TIME,因为它处理了可选的秒数小数部分——而且小数位数似乎也是可变的,而且这个内置格式化程序已经处理了这些细节给你。


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

    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();
    

    您可以进行的另一项改进是将格式化程序更改为static finalbecause this class is immutable and thread-safe

    【讨论】:

      【解决方案3】:

      这只是答案的四分之一。我既没有使用 Kotlin 也没有使用过 Jackson,但我有几个 Java 解决方案我想贡献一下。如果您能以某种方式将它们放入一个完整的解决方案中,我会很高兴。

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

      在我的 Java 9 (9.0.4) 上,单参数 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。我们需要按从最长到最短的顺序尝试它们,以确保在所有情况下都解析所有文本。

      【讨论】:

      • 如果可能的话,我不想使用正则表达式替换东西......但你的第二个解决方案效果很好!
      • 我说这是一个黑客。很高兴您更喜欢第二个 sn-p。
      猜你喜欢
      • 2018-10-17
      • 1970-01-01
      • 1970-01-01
      • 2021-12-15
      相关资源
      最近更新 更多