杰克逊没有正确解析时间戳

时间:2017-09-22 06:53:40

标签: java jackson timestamp datetime-parsing jackson-dataformat-xml

我在使用Jackson 2.8.5从XML文件解析java.sql.Timestamp时遇到问题。不知何故,毫秒在左侧用零填充。

这是一个最小的例子,显示:

public class Foo {

    @JacksonXmlProperty(localName = "ts")
    Timestamp ts;

    public static void main(String[] args) throws IOException {
        String xml = "<Foo><ts>2017-09-21T11:25:32.1Z</ts></Foo>"
        Foo foo = new XmlMapper().readValue(xml, Foo.class);
        System.out.println("foo.ts = " + foo.ts);
    }
}
  

foo.ts = 2017-09-21 11:25:32.001

然而,如果我手动解析字符串,我会得到预期值

System.out.println(Instant.parse("2017-09-21T11:25:32.1Z"));
  

2017-09-21 11:25:32.1

1 个答案:

答案 0 :(得分:2)

这似乎是SimpleDateFormat(杰克逊在内部使用)的问题,如stated in this answer。我也用普通Java(没有Jackson)进行了测试,也出现了错误:

String s = "2017-09-21T11:25:32.1Z";
Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SX").parse(s);
System.out.println(new Timestamp(date.getTime())); // 2017-09-21 08:25:32.001

如果您可以更改xml,则在输入(2017-09-21T11:25:32.100Z)中添加零可以正常工作。

另一种方法是为您的字段编写自定义反序列化程序(使用Timestamp类中的正确方法从Instant转换):

public class CustomTimestampDeserializer extends JsonDeserializer<Timestamp> {

    @Override
    public Timestamp deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        return Timestamp.from(Instant.parse(p.getText()));
    }
}

然后您注释该字段以使用此自定义反序列化器:

@JacksonXmlProperty(localName = "ts")
@JsonDeserialize(using = CustomTimestampDeserializer.class)
Timestamp ts;

现在,在打印Timestamp时,您会得到正确的值:

  

2017-09-21 08:25:32.1

等一下,为什么小时为08 - 那是因为当你System.out.println Timestamp时,它隐含地调用了toString()方法,这个方法将Timestamp转换为JVM默认时区(在我的情况下,它是America/Sao_Paulo,所以它是正确的,因为圣保罗08:25 AM相当于UTC的11:25 AM )。但Timestamp保留的值是正确的。

您可以在this article中详细了解toString()的{​​{1}}行为 - 它谈到java.util.Date,但这个想法是一样的(特别是因为Timestamp扩展了Date,所以它有同样的问题。)

要将其序列化回xml,您还可以配置自定义序列化程序:

public class CustomTimestampSerializer extends JsonSerializer<Timestamp> {

    @Override
    public void serialize(Timestamp value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
        gen.writeString(value.toInstant().toString());
    }
}

然后您注释该字段以使用此序列化程序:

@JacksonXmlProperty(localName = "ts")
@JsonDeserialize(using = CustomTimestampDeserializer.class)
@JsonSerialize(using = CustomTimestampSerializer.class)
Timestamp ts;

只是一个细节:Instant.toString()会产生2017-09-21T11:25:32.100Z。如果您最后不想要这些额外的零,则可以使用DateTimeFormatter自定义格式。所以自定义序列化器将是这样的:

public class CustomTimestampSerializer extends JsonSerializer<Timestamp> {

    private DateTimeFormatter fmt = new DateTimeFormatterBuilder()
        // date/time
        .appendPattern("yyyy-MM-dd'T'HH:mm:ss")
        // nanoseconds without leading zeroes (from 0 to 9 digits)
        .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
        // offset (Z)
        .appendOffsetId()
        // create formatter (set zone to UTC to properly format the Instant)
        .toFormatter().withZone(ZoneOffset.UTC);

    @Override
    public void serialize(Timestamp value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
        gen.writeString(fmt.format(value.toInstant()));
    }
}

这会将时间戳打印为2017-09-21T11:25:32.1Z