Джексон неправильно анализирует метку времени
У меня проблемы с анализом java.sql.Timestamp
из файлов XML с использованием Jackson 2.8.5. Каким-то образом миллисекунды дополняются нулями с левой стороны.
Вот минимальный пример, показывающий, что:
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 ответ
Это кажется проблемой с SimpleDateFormat
(который используется внутри Джексона), как указано в этом ответе. Я также сделал тест с простой Java (без Джексона), и ошибка также происходит:
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
, так что это правильно, потому что 8:25 утра в Сан-Паулу эквивалентно 11:25 утра в UTC). Но ценность сохраняется Timestamp
верно.
Вы можете прочитать больше об этом поведении toString()
в этой статье - речь идет о 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
,