Джексон неправильно анализирует метку времени

У меня проблемы с анализом 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,

Другие вопросы по тегам