使用Jackson将Java8 LocalDateTime序列化为UTC时间戳

15

我刚刚将许多日期转换为新的(相对较新)java 8时间包中的LocalDateTime。到目前为止,我很喜欢这个更改,直到我开始尝试序列化和反序列化。

如何配置Jackson以支持它们:

LocalDateTime --序列化--> UTC时间戳 --反序列化--> LocalDateTime?

有大量关于转换为格式化字符串的材料,但我似乎找不到针对utc时间戳的开箱即用的解决方案。


这应该有所帮助:https://fasterxml.github.io/jackson-databind/javadoc/2.8/com/fasterxml/jackson/databind/SerializationFeature.html#WRITE_DATES_AS_TIMESTAMPS - assylias
3
如果使用的是UTC时间戳,为什么不使用Instant而不是LocalDateTime呢? - ernest_k
@assylias,信不信由你,这并不起作用:WRITE_DATES_AS_TIMESTAMPS适用于日期。当为LocalDateTimes打开时,Jackson将其序列化为值数组。 - Daniel Patrick
@ernest_k,我有其他使用LocalDateTime的原因。但第三方API需要将其格式化为时间戳整数,所以我需要将其转换为与他们的系统交互。 - Daniel Patrick
你尝试过FasterXML/jackson-modules-java8吗? - Ole V.V.
1
@DanielPatrick 如果您注册一个 new JavaTimeModule(),它应该可以工作 - 请参见此处:http://fasterxml.github.io/jackson-datatype-jsr310/javadoc/2.6/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.html - assylias
4个回答

11
您可以自定义LocalDateTime的序列化器和反序列化器,例如:
CustomLocalDateTimeSerializer
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;

public class CustomLocalDateTimeSerializer extends StdSerializer<LocalDateTime> {

    protected CustomLocalDateTimeSerializer(Class<LocalDateTime> t) {
        super(t);
    }

    protected CustomLocalDateTimeSerializer() {
        this(null);
    }

    @Override
    public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider sp)
            throws IOException {
        Long epoch = value.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
        gen.writeString(epoch.toString());
    }
}

CustomLocalDateTimeDesSerializer:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

class CustomLocalDateTimeDesSerializer extends StdDeserializer<LocalDateTime> {

    protected CustomLocalDateTimeDesSerializer() {
        this(null);
    }

    protected CustomLocalDateTimeDesSerializer(Class<LocalDateTime> t) {
        super(t);
    }

    @Override
    public LocalDateTime deserialize(JsonParser jsonparser, DeserializationContext context)
            throws IOException {
        Long timestamp = Long.parseLong(jsonparser.getText());
        return LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
    }
}

并使用它们:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import java.time.LocalDateTime;

public class RestObject {

    private LocalDateTime timestamp = LocalDateTime.now();

    @JsonSerialize(using = CustomLocalDateTimeSerializer.class)
    @JsonDeserialize(using = CustomLocalDateTimeDesSerializer.class)
    public LocalDateTime getTimestamp() {
        return timestamp;
    }

    public static void main(String[] args) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        // {"timestamp":"1549026058"}
        System.out.println(objectMapper.writeValueAsString(new RestObject()));
    }
}

1
这个可行...谢谢!真不敢相信Jackson没有配置来允许LocalDateTime序列化与他们旧的Date序列化兼容。 - Daniel Patrick
@DanielPatrick 我不确定这是否是最合适的方式,但这是可行的方法。 - xingbin

8
以下解决方案解决了将LocalDateTime序列化/反序列化为时间戳的任务,至少与spring-boot v1.5相关,并包含以下在@xingbin的答案中未考虑的要点:
  • 如果需要与java.util.Date具有相同的行为,则必须使用toInstant().toEpochMilli()而不是toInstant().getEpochSecond()
  • 检查需要反序列化的值是否为null
  • 可选点:指定Jackson ObjectMapper的此序列化/反序列化配置。
时间戳序列化类:
public class LocalDateTimeSerializer extends StdSerializer<LocalDateTime> {
    private static final ZoneId DEFAULT_ZONE_ID = ZoneId.of("UTC");

    public LocalDateTimeSerializer() {
        super(LocalDateTime.class);

    }

    @Override
    public void serialize(final LocalDateTime value,
                          final JsonGenerator generator,
                          final SerializerProvider provider) throws IOException {
        if (value != null) {
            final long mills = value.atZone(DEFAULT_ZONE_ID).toInstant().toEpochMilli();
            generator.writeNumber(mills);
        } else {
            generator.writeNull();
        }
    }
}

时间戳反序列化类:
public class LocalDateTimeDeserializer extends StdDeserializer<LocalDateTime> {
    private static final ZoneId DEFAULT_ZONE_ID = ZoneId.of("UTC");

    public LocalDateTimeDeserializer() {
        super(LocalDateTime.class);
    }

    @Override
    public LocalDateTime deserialize(final JsonParser parser,
                                     final DeserializationContext context) throws IOException {
        final long value = parser.getValueAsLong();
        return LocalDateTime.ofInstant(Instant.ofEpochMilli(value), DEFAULT_ZONE_ID);
    }
}

对象映射器配置:

@Configuration
public class ObjectMapperConfiguration {
    @Bean
    public ObjectMapper objectMapper() {
        final ObjectMapper objectMapper = new ObjectMapper();
        final SimpleModule module = new SimpleModule();
        module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
        module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
        objectMapper.registerModule(module);
        return objectMapper;
    }
}

1
这是简单高效的方法:

在此列出:

@JsonFormat(shape= JsonFormat.Shape.STRING, pattern="EEE MMM dd HH:mm:ss Z yyyy")
@JsonProperty("created_at") 
ZonedDateTime created_at;

这是一个问题中的引用:Jackson deserialize date from Twitter to `ZonedDateTime`,因此可能是一个重复的问题。


0

不必手动重写所有内容,您可以利用{{link1:JavaTimeModule}}:

ObjectMapper om = new ObjectMapper();
om.registerModule(new JavaTimeModule());
om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);

1
链接中有解释。 - assylias
1
根据我对问题的理解,链接中的解释似乎解释了为什么这对于“LocalDateTime”不起作用,因此适用于此问题。 - Vic Seedoubleyew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接