如何使用Jackson与java.time解析多种ISO类日期时间格式?
解决Spring Boot + Jackson兼容多格式ISO时间的方案
我在Spring Boot 2.x项目里刚好解决过完全一样的问题——处理外部方传来的各种“ISO-ish”时间格式,用Jackson和java.time.OffsetDateTime来兼容所有变种。下面是实战可行的解决方案:
核心思路
Jackson默认的OffsetDateTime序列化/反序列化只严格遵循标准ISO-8601格式,无法兼容你提到的时区偏移变种(比如+00、+0000)和不同精度的毫秒位。我们需要自定义日期格式化器,并全局配置到Jackson的ObjectMapper中,让它能自动识别所有目标格式。
步骤1:自定义兼容所有格式的DateTimeFormatter
用DateTimeFormatterBuilder组合所有需要支持的格式,实现“可选匹配”:
private static final DateTimeFormatter CUSTOM_OFFSET_DATE_TIME_FORMATTER = new DateTimeFormatterBuilder() // 匹配基础的本地日期时间部分(yyyy-MM-dd'T'HH:mm:ss) .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) // 可选的毫秒部分:支持0-3位(比如.410、.41、无毫秒) .optionalStart() .appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true) .optionalEnd() // 可选的时区偏移格式1:+HH:MM(比如+00:00) .optionalStart() .appendOffset("+HH:MM", "+00:00") .optionalEnd() // 可选的时区偏移格式2:+HHMM(比如+0000) .optionalStart() .appendOffset("+HHMM", "+0000") .optionalEnd() // 可选的时区偏移格式3:+HH(比如+00) .optionalStart() .appendOffset("+HH", "+00") .optionalEnd() // 可选的UTC标识Z .optionalStart() .appendLiteral('Z') .optionalEnd() .toFormatter();
这个格式化器会依次尝试匹配所有你列出的时间格式,完全覆盖需求。
步骤2:全局配置Jackson ObjectMapper
通过Spring Boot的配置类,把自定义格式化器注册到Jackson的JavaTimeModule中,替换默认的OffsetDateTime序列化/反序列化逻辑:
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.JsonDeserializer; import com.fasterxml.jackson.databind.ser.std.JsonSerializer; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.io.IOException; import java.time.OffsetDateTime; import java.time.format.DateTimeParseException; @Configuration public class JacksonDateTimeConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); JavaTimeModule timeModule = new JavaTimeModule(); // 注册自定义序列化器:统一输出标准ISO格式(比如2018-01-01T15:56:31.410+00:00) timeModule.addSerializer(OffsetDateTime.class, (JsonSerializer<OffsetDateTime>) (value, gen, serializers) -> { gen.writeString(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); }); // 注册自定义反序列化器:用我们的格式化器解析所有变种格式 timeModule.addDeserializer(OffsetDateTime.class, (JsonDeserializer<OffsetDateTime>) (p, ctxt) -> { String dateStr = p.getText().trim(); try { return OffsetDateTime.parse(dateStr, CUSTOM_OFFSET_DATE_TIME_FORMATTER); } catch (DateTimeParseException e) { throw new IOException("无法解析时间格式:" + dateStr, e); } }); mapper.registerModule(timeModule); // 禁用时间戳格式,强制输出字符串格式 mapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return mapper; } // 上面定义的自定义格式化器 private static final DateTimeFormatter CUSTOM_OFFSET_DATE_TIME_FORMATTER = new DateTimeFormatterBuilder() .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) .optionalStart() .appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true) .optionalEnd() .optionalStart() .appendOffset("+HH:MM", "+00:00") .optionalEnd() .optionalStart() .appendOffset("+HHMM", "+0000") .optionalEnd() .optionalStart() .appendOffset("+HH", "+00") .optionalEnd() .optionalStart() .appendLiteral('Z') .optionalEnd() .toFormatter(); }
步骤3:验证测试
创建一个测试用的请求类和接口,验证所有格式是否能正确解析:
// 请求类 public class TimeTestRequest { private OffsetDateTime time1; // 2018-01-01T15:56:31.410Z private OffsetDateTime time2; // 2018-01-01T15:56:31.41Z private OffsetDateTime time3; // 2018-01-01T15:56:31Z private OffsetDateTime time4; // 2018-01-01T15:56:31+00:00 private OffsetDateTime time5; // 2018-01-01T15:56:31+0000 private OffsetDateTime time6; // 2018-01-01T15:56:31+00 // Getters & Setters } // 测试接口 import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class TimeTestController { @PostMapping("/test-time-formats") public ResponseEntity<TimeTestRequest> testTimeFormats(@RequestBody TimeTestRequest request) { // 返回解析后的结果,会自动序列化为标准ISO格式 return ResponseEntity.ok(request); } }
用Postman或curl发送包含所有格式的JSON请求,接口应该能正确解析所有字段,并返回统一的标准格式。
额外提示
- 如果只是个别字段需要兼容多格式,也可以用
@JsonFormat注解(格式字符串用方括号包裹可选格式),但全局配置更适合大量使用OffsetDateTime的场景:@JsonFormat(pattern = "[yyyy-MM-dd'T'HH:mm:ss][yyyy-MM-dd'T'HH:mm:ss.SSS][yyyy-MM-dd'T'HH:mm:ss.SS][yyyy-MM-dd'T'HH:mm:ss.S][yyyy-MM-dd'T'HH:mm:ssZ][yyyy-MM-dd'T'HH:mm:ss+HH:mm][yyyy-MM-dd'T'HH:mm:ss+HHmm][yyyy-MM-dd'T'HH:mm:ss+HH]") private OffsetDateTime timestamp; - 确保你的Spring Boot项目已经引入了
jackson-datatype-jsr310依赖(Spring Boot的starter-web默认包含)。
内容的提问来源于stack exchange,提问作者Bastian Voigt
相关产品推荐
相关产品推荐

