如何使用 Jackson 和 java.time 解析不同的 ISO 日期/时间格式?
Posted
技术标签:
【中文标题】如何使用 Jackson 和 java.time 解析不同的 ISO 日期/时间格式?【英文标题】:How to parse different ISO date/time formats with Jackson and java.time? 【发布时间】: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 有一些变化空间。我相信你所有的例子都符合。 【参考方案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 final
、because this class is immutable and thread-safe。
【讨论】:
【参考方案3】:这只是答案的四分之一。我既没有使用 Kotlin 也没有使用过 Jackson,但我有几个 Java 解决方案我想贡献一下。如果您能以某种方式将它们放入一个完整的解决方案中,我会很高兴。
String modifiedEx = ex.replaceFirst("(\\d2)(\\d2)$", "$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));
模式XXX
、XX
和X
分别匹配+00:00
、+0000
和+00
。我们需要按从最长到最短的顺序尝试它们,以确保在所有情况下都解析所有文本。
【讨论】:
如果可能的话,我不想使用正则表达式替换东西......但你的第二个解决方案效果很好! 我说这是一个黑客。很高兴您更喜欢第二个 sn-p。以上是关于如何使用 Jackson 和 java.time 解析不同的 ISO 日期/时间格式?的主要内容,如果未能解决你的问题,请参考以下文章
如何正确地在Spring Data JPA和Jackson中用上Java 8的时间相关API(即JSR 310也即java.time包下的众神器)
使用 Spring Boot 2 和 Kotlin 进行 Jackson 反序列化,无法构造 `java.time.LocalDate` 的实例
Jackson , java.time , ISO 8601 , 无毫秒序列化
Spring Boot 问题使用 Jackson 序列化 java.time.LocalDateTime 以返回 ISO-8601 JSON 时间戳?
如何将 graphql-java-extended-scalars DateTime 与 Jackson 一起使用
是否有任何选项可以在 Spring Boot 中使用 Jackson 为 java.time.* 包注册一次 Serializer/Deserializer?