Reputation: 5325
I'm having trouble parsing java.sql.Timestamp
from XML files using Jackson 2.8.5. Somehow the milliseconds are padded with zeros on the left side.
Here is a minimal example showing that :
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
Whereas if I parse the string manually, I get the expected value
System.out.println(Instant.parse("2017-09-21T11:25:32.1Z"));
2017-09-21 11:25:32.1
Upvotes: 2
Views: 1908
Reputation:
That seems to be a problem with SimpleDateFormat
(which is used internally by Jackson), as stated in this answer. I've also made a test with plain Java (without Jackson) and the error also occurs:
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
If you can change the xml, adding zeroes to the input (2017-09-21T11:25:32.100Z
) works.
Another alternative is to write a custom deserializer for your field (using the proper methods in Timestamp
class to convert from 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()));
}
}
Then you annotate the field to use this custom deserializer:
@JacksonXmlProperty(localName = "ts")
@JsonDeserialize(using = CustomTimestampDeserializer.class)
Timestamp ts;
Now, when printing the Timestamp
, you get the correct value:
2017-09-21 08:25:32.1
Wait a minute, why the hour is 08
? - That's because when you System.out.println
a Timestamp
, it implicity calls the toString()
method, and this method converts the Timestamp
to the JVM default timezone (in my case, it's America/Sao_Paulo
, so it's correct, because 08:25 AM in São Paulo is equivalent to 11:25 AM in UTC). But the value kept by the Timestamp
is correct.
You can read more about this behaviour of toString()
in this article - it talks about java.util.Date
, but the idea is the same (specially because Timestamp
extends Date
, so it has the same problems).
To serialize it back to xml, you can configure a custom serializer as well:
public class CustomTimestampSerializer extends JsonSerializer<Timestamp> {
@Override
public void serialize(Timestamp value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeString(value.toInstant().toString());
}
}
Then you annotate the field to use this serializer:
@JacksonXmlProperty(localName = "ts")
@JsonDeserialize(using = CustomTimestampDeserializer.class)
@JsonSerialize(using = CustomTimestampSerializer.class)
Timestamp ts;
Just a detail: Instant.toString()
will result in 2017-09-21T11:25:32.100Z
. If you don't want those extra zeroes in the end, you can customize the format using a DateTimeFormatter
. So the custom serializer will be like this:
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()));
}
}
This will print the timestamp as 2017-09-21T11:25:32.1Z
.
Upvotes: 2