【问题标题】:Force Milliseconds When Serializing Instant to ISO8601 using Jackson使用 Jackson 将 Instant 序列化为 ISO8601 时强制毫秒
【发布时间】:2018-05-10 04:10:14
【问题描述】:

在我使用Spring Boot 2.0.0.M6Spring Framework 5.0.1.RELEASEJackson 2.9.2 的项目中,我有一些与JSON 序列化使用Jackson 相关的问题。

我在application.properties中配置了以下杰克逊相关设置:

spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false

序列化主要根据我的需要工作。不过,我注意到如果是000,Jackson 似乎会截断毫秒。

测试 1:序列化 Instant,毫秒设置为 000

  • 使用Instant.parse("2017-09-14T04:28:48.000Z") 初始化即时字段
  • 使用 Jackson 对其进行序列化
  • 输出将是"2017-09-14T04:28:48Z"

测试 2:序列化 Instant,毫秒设置为某个非000 值:

  • 使用Instant.parse("2017-09-14T04:28:48.100Z") 初始化即时字段
  • 使用 Jackson 对其进行序列化
  • 输出将是"2017-09-14T04:28:48.100Z"

问题

  • 这种行为是设计使然吗?
  • 我可以做些什么来强制序列化000

【问题讨论】:

标签: java json spring spring-boot jackson2


【解决方案1】:

这个here* 似乎存在一个杰克逊问题。该链接包含两个解决方法

解决方法 1

 ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new JavaTimeModule());
    SimpleModule module = new SimpleModule();
    module.addSerializer(ZonedDateTime.class, new JsonSerializer<ZonedDateTime>() {
        @Override
        public void serialize(ZonedDateTime zonedDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            jsonGenerator.writeString(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZZZ").format(zonedDateTime));
        }
    });
    objectMapper.registerModule(module);
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    objectMapper.enable(SerializationFeature.INDENT_OUTPUT);

解决方法 2

JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(ZonedDateTime.class,
  new ZonedDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")));
ObjectMapper mapper = new ObjectMapper().registerModule(javaTimeModule);

*Link 已失效,因为他们弃用了 FasterXML/jackson-datatype-jsr310 并将其移至 jackson-modules-java8。见https://github.com/FasterXML/jackson-modules-java8/issues/76

【讨论】:

  • 不幸的是,我的 Instant 在序列化时仍然没有 .000。尝试了您的第一个解决方法。我创建了一个配置类并使用 Primary 注释添加了一个 ObjectMapper bean 来覆盖自动配置的对象映射器。
  • 我试过杰克逊 2.9.5。这个问题仍然没有解决。但是您的第一个解决方法有效,谢谢
  • 解决方法 1 对我有用。你应该注意注册模块的顺序。
  • 如果毫秒和秒分别为 000 和 00,则问题会进一步发展到秒。如果我使用 2020-02-04T08:26.00.000Z 我的输出是 2020-02-04T08:26Z。有什么解决方法吗?
【解决方案2】:

Sean Carroll 提到的两种变通方法都不适合我。我最终为 Instant 编写了自己的序列化程序。

final ObjectMapper mapper = new ObjectMapper();
final JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(Instant.class, new KeepMillisecondInstantSerializer());
mapper.registerModule(javaTimeModule);

public class KeepMillisecondInstantSerializer extends JsonSerializer<Instant> {

    private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
            .withZone(ZoneId.of("UTC"));

    @Override
    public void serialize(final Instant instant, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) throws IOException {
        final String serializedInstant = dateTimeFormatter.format(instant);
        jsonGenerator.writeString(serializedInstant);
    }
}

我猜默认是 Jackson use Instant.toString() method to serialize Instant objects。我也找到了some discussions about Instant.toString() method on StackOverflow

【讨论】:

    【解决方案3】:

    我用这个方法解决:

    ObjectMapper objectMapper = new ObjectMapper();
    JavaTimeModule module = new JavaTimeModule();
    module.addSerializer(Instant.class, new InstantSerializerWithMilliSecondPrecision());
    objectMapper.registerModule(module);
    

    对于 InstantSerializerWithMilliSecondPrecision,我使用了这个:

    public class InstantSerializerWithMilliSecondPrecision extends InstantSerializer {
    
        public InstantSerializerWithMilliSecondPrecision() {
            super(InstantSerializer.INSTANCE, false, new DateTimeFormatterBuilder().appendInstant(3).toFormatter());
        }
    }
    

    现在即时序列化始终包含毫秒。示例:2019-09-27T02:59:59.000Z

    【讨论】:

      【解决方案4】:

      除了修复 Jackson 库的错误之外,以下可能是一个快速的解决方法: 在你有 Timestamp 变量的 POJO 类中创建一个字符串变量:

      private Timestamp createTimeStamp;
      
      private String stringCreateTimeStamp;   
      

      将时间戳值捕获为字符串:

      listOfPojo.forEach(pojo-> {
                  pojo.setStringCreateTimeStamp(request.getcreateTimeStamp().toString());
              });
      

      请参考https://www.baeldung.com/java-string-to-timestamp 了解转化情况

      【讨论】:

        【解决方案5】:

        使用 LocalDateTimeZonedDateTime 类的自定义序列化程序来解决这个问题。

        我的解决方案适合我,因为我在 API 响应中只使用这两个类来表示日期和时间!我不使用 Instant 或 Date 所以注意它。

        @Configuration
        class JacksonConfig {
        
        @Bean
        fun objectMapper(): ObjectMapper {
            val mapper = ObjectMapper()
            val javaTimeModule = JavaTimeModule().apply {
                addSerializer(LocalDateTime::class.java, KeepMillisecondLocalDateTimeSerializer())
                addSerializer(ZonedDateTime::class.java, KeepMillisecondZonedDateTimeSerializer())
            }
            mapper.registerModule(javaTimeModule)
            return mapper
        }
        
        class KeepMillisecondZonedDateTimeSerializer : JsonSerializer<ZonedDateTime>() {
            private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
        
            @Throws(IOException::class)
            override fun serialize(
                value: ZonedDateTime,
                jsonGenerator: JsonGenerator,
                serializerProvider: SerializerProvider?
            ) {
                jsonGenerator.writeString(formatter.format(value))
            }
        }
        
        class KeepMillisecondLocalDateTimeSerializer : JsonSerializer<LocalDateTime>() {
            private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")
        
            @Throws(IOException::class)
            override fun serialize(
                value: LocalDateTime,
                jsonGenerator: JsonGenerator,
                serializerProvider: SerializerProvider?
            ) {
                jsonGenerator.writeString(formatter.format(value))
            }
        }
        }
        

        【讨论】:

          【解决方案6】:

          如果您尝试在 Spring Boot 中执行此操作并希望使用 @Gustavo 的答案。

          import com.fasterxml.jackson.databind.Module;
          import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
          import org.springframework.context.annotation.Configuration;
          
          @Configuration
          public class AppConfig {
          
              @Bean
              public Module javaTimeModule() {
                  JavaTimeModule module = new JavaTimeModule();
                  module.addSerializer(new InstantSerializerWithMilliSecondPrecision());
                  return module;
              }
          
          }
          

          【讨论】:

            猜你喜欢
            • 2016-07-15
            • 2017-12-29
            • 1970-01-01
            • 2021-11-30
            • 2019-07-16
            • 2017-03-22
            • 2014-11-09
            • 2021-07-03
            • 1970-01-01
            相关资源
            最近更新 更多