如何使用 Jackson 和 java.time 解析不同的 ISO date/time 格式?
How to parse different ISO date/time formats with Jackson and java.time?
Our 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
我们的堆栈是 Spring Boot 2.0 with Jackson ObjectMapper。在我们的数据 类 中,我们经常使用类型 java.time.OffsetDateTime
。
几位开发人员已尝试实现解析上述所有格式的解决方案,none 已成功。特别是带有冒号的第四个变体(00:00
)似乎无法解析。
如果该解决方案无需在我们模型的每个 date/time 字段上放置注释即可工作,那就太好了。
尊敬的社区,您有解决方案吗?
一种替代方法是创建自定义反序列化器。首先注释相应的字段:
@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);
}
}
我还使用了 built-in 常量 DateTimeFormatter.ISO_LOCAL_DATE_TIME
因为它处理可选的秒数 - 小数位数似乎也是可变的,而这个 built-in 格式化程序已经为您处理了这些细节。
我正在使用 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.
这只是大约四分之一的答案。我既没有使用 Kotlin 也没有使用 Jackson 的经验,但我在 Java 中有几个解决方案我想贡献出来。如果您能以某种方式将它们纳入一个完整的解决方案,我会很高兴。
String modifiedEx = ex.replaceFirst("(\d{2})(\d{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));
模式 XXX
、XX
和 X
分别匹配 +00:00
、+0000
和 +00
。我们需要按照从最长到最短的顺序尝试它们,以确保在所有情况下都能解析所有文本。
非常感谢您的所有意见!
我选择了 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 class 路径中添加了一个 @Configuraton
class 以使其在没有任何数据注释的情况下工作 classes:
@Configuration
open class JacksonConfig {
@Bean
open fun jacksonCustomizer() = Jackson2ObjectMapperBuilderCustomizer {
it.deserializers(DefensiveIsoOffsetDateTimeDeserializer())
it.serializers(OffsetDateTimeSerializer())
}
}
Our 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
我们的堆栈是 Spring Boot 2.0 with Jackson ObjectMapper。在我们的数据 类 中,我们经常使用类型 java.time.OffsetDateTime
。
几位开发人员已尝试实现解析上述所有格式的解决方案,none 已成功。特别是带有冒号的第四个变体(00:00
)似乎无法解析。
如果该解决方案无需在我们模型的每个 date/time 字段上放置注释即可工作,那就太好了。
尊敬的社区,您有解决方案吗?
一种替代方法是创建自定义反序列化器。首先注释相应的字段:
@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);
}
}
我还使用了 built-in 常量 DateTimeFormatter.ISO_LOCAL_DATE_TIME
因为它处理可选的秒数 - 小数位数似乎也是可变的,而这个 built-in 格式化程序已经为您处理了这些细节。
我正在使用 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.
这只是大约四分之一的答案。我既没有使用 Kotlin 也没有使用 Jackson 的经验,但我在 Java 中有几个解决方案我想贡献出来。如果您能以某种方式将它们纳入一个完整的解决方案,我会很高兴。
String modifiedEx = ex.replaceFirst("(\d{2})(\d{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));
模式 XXX
、XX
和 X
分别匹配 +00:00
、+0000
和 +00
。我们需要按照从最长到最短的顺序尝试它们,以确保在所有情况下都能解析所有文本。
非常感谢您的所有意见!
我选择了 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 class 路径中添加了一个 @Configuraton
class 以使其在没有任何数据注释的情况下工作 classes:
@Configuration
open class JacksonConfig {
@Bean
open fun jacksonCustomizer() = Jackson2ObjectMapperBuilderCustomizer {
it.deserializers(DefensiveIsoOffsetDateTimeDeserializer())
it.serializers(OffsetDateTimeSerializer())
}
}