You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Quarkus中自定义OffsetDateTime反序列化失效,如何正确配置?

解决Quarkus中OffsetDateTime自定义反序列化器不生效问题

问题场景

前端通过datetime-local控件发送格式为2024-06-01T05:55:04的日期,后端Quarkus的REST控制器接收时无法将该格式解析为OffsetDateTime,返回HTTP 400错误。调试发现默认的InstantDeserializer被调用,自定义反序列化器未生效。

现有代码结构

REST控制器与实体类

@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ExampleResource {
  @POST
  public RestResponse<Example> create(Example example) {
    return RestResponse.ok(exampleService.create(example));
  }
}

@Data
public class Example {
    @JsonProperty("creationTime")
    private OffsetDateTime creationTime;  // 前端传入2024-06-01T05:55:04
}

尝试过的无效配置

  1. 实现ObjectMapperCustomizer
@Singleton
public class ObjectMapperConfig implements ObjectMapperCustomizer {

  public void customize(ObjectMapper mapper) {
    var module = new SimpleModule();
    module.addDeserializer(OffsetDateTime.class, new CustomOffsetDateTimeDeserializer());
    mapper.registerModule(module);
  }

  static class CustomOffsetDateTimeDeserializer extends JsonDeserializer<OffsetDateTime> {

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");

    // 调试发现反序列化时未调用此方法
    @Override
    public OffsetDateTime deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
      String dateString = p.getText();
      try {
        return OffsetDateTime.parse(dateString);
      } catch (Exception e) {
        var localDateTime = LocalDateTime.parse(dateString, FORMATTER);
        return localDateTime.atOffset(ZoneOffset.systemDefault().getRules().getOffset(localDateTime));
      }
    }

  }

}
  1. 重写默认ObjectMapper Bean
@Produces
@Singleton
public ObjectMapper objectMapper() {
  var module = new SimpleModule();
  module.addDeserializer(OffsetDateTime.class, new CustomOffsetDateTimeDeserializer());
  var objectMapper = new ObjectMapper();
  objectMapper.registerModule(module);
  return objectMapper;
}

解决方案

方法一:替换JavaTimeModule中的默认反序列化器

Quarkus默认集成的JavaTimeModule内置了OffsetDateTime的反序列化器,直接添加自定义实现不会覆盖默认逻辑,需先移除默认处理器再添加自定义类:

@Singleton
public class ObjectMapperConfig implements ObjectMapperCustomizer {

  @Override
  public void customize(ObjectMapper mapper) {
    // 获取已注册的JavaTimeModule,不存在则新建
    JavaTimeModule javaTimeModule = mapper.getRegisteredModule(JavaTimeModule.class);
    if (javaTimeModule == null) {
      javaTimeModule = new JavaTimeModule();
      mapper.registerModule(javaTimeModule);
    }
    // 移除默认的OffsetDateTime反序列化器
    javaTimeModule.removeDeserializer(OffsetDateTime.class);
    // 添加自定义反序列化器
    javaTimeModule.addDeserializer(OffsetDateTime.class, new CustomOffsetDateTimeDeserializer());
  }

  static class CustomOffsetDateTimeDeserializer extends JsonDeserializer<OffsetDateTime> {

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");

    @Override
    public OffsetDateTime deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
      String dateString = p.getText().trim();
      try {
        // 优先解析带时区偏移的标准格式
        return OffsetDateTime.parse(dateString);
      } catch (DateTimeParseException e) {
        // 解析不带偏移的格式,转换为系统默认时区的OffsetDateTime
        LocalDateTime localDateTime = LocalDateTime.parse(dateString, FORMATTER);
        return localDateTime.atOffset(ZoneOffset.systemDefault().getRules().getOffset(localDateTime));
      }
    }
  }
}

方法二:字段级直接指定反序列化器

若仅需特定字段生效,可跳过全局配置,直接在实体类字段上添加注解:

@Data
public class Example {
    @JsonProperty("creationTime")
    @JsonDeserialize(using = CustomOffsetDateTimeDeserializer.class)
    private OffsetDateTime creationTime;
}

// 独立的自定义反序列化器类
public class CustomOffsetDateTimeDeserializer extends JsonDeserializer<OffsetDateTime> {

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");

    @Override
    public OffsetDateTime deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
      String dateString = p.getText().trim();
      try {
        return OffsetDateTime.parse(dateString);
      } catch (DateTimeParseException e) {
        LocalDateTime localDateTime = LocalDateTime.parse(dateString, FORMATTER);
        return localDateTime.atOffset(ZoneOffset.systemDefault().getRules().getOffset(localDateTime));
      }
    }
}

方法三:全局配置Jackson日期解析规则

无需编写自定义反序列化器,直接修改JavaTimeModule的解析逻辑:

@Singleton
public class ObjectMapperConfig implements ObjectMapperCustomizer {

  @Override
  public void customize(ObjectMapper mapper) {
    JavaTimeModule javaTimeModule = new JavaTimeModule();
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
    
    // 替换OffsetDateTime的默认反序列化逻辑
    javaTimeModule.addDeserializer(OffsetDateTime.class, new InstantDeserializer.OffsetDateTimeDeserializer(
            InstantDeserializer.OffsetDateTimeDeserializer.INSTANCE,
            formatter,
            (parser, context, f) -> {
              String str = parser.getText().trim();
              try {
                return OffsetDateTime.parse(str, f);
              } catch (DateTimeParseException e) {
                LocalDateTime localDateTime = LocalDateTime.parse(str, f);
                return localDateTime.atOffset(ZoneOffset.systemDefault().getRules().getOffset(localDateTime));
              }
            }
    ));
    
    mapper.registerModule(javaTimeModule);
    // 关闭未知属性的严格校验,避免额外报错
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
  }
}

关键说明

  • Quarkus默认启用JavaTimeModule,该模块的内置反序列化器优先级高于自定义模块,必须显式移除默认实现才能生效。
  • 全局配置类需添加@Singleton注解,确保被Quarkus扫描并加载。
  • 调试时可开启Jackson日志:quarkus.log.category."com.fasterxml.jackson".level=DEBUG,查看具体反序列化错误信息。

内容的提问来源于stack exchange,提问作者Politechniczny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 01:45:05