如何在 Java 中使用 Jackson Annotation 按原样反序列化时间戳?
How to deserialize a Timestamp as-is using Jackson Annotation in Java?
我的 Java Bean 中有一个字段,如下所示,其中 @JsonFormat
是一个 jackson 注释:
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm")
private Timestamp createdOn;
当我 return Timestamp
时,它总是在我的 JSON 响应中转换为 UTC 时间。让我们说一下格式化我的 Timestamp ,我得到 2017-09-13 15:30
但我的响应被 returned 为 2017-09-13 10:00
.
我知道这是因为 Jackson 注释默认采用系统时区并将时间戳转换为 UTC。 (在我的例子中,服务器属于 Asia/Calcutta
时区,因此偏移量为 +05:30
并且 Jackson 映射器减去 5 小时 30 分钟以将时间戳转换为 UTC)
有没有办法按原样 return 时间戳,即 2017-09-13 15:30
而不是 2017-09-13 10:00
?
您可以使用 timeZone
来明确指定您想要的时区:
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm", timezone="Asia/Calcutta")
private Timestamp createdOn;
我在这里做了一个测试,将 JVM 默认时区更改为 Asia/Calcutta
并创建一个带有 java.sql.Timestamp
字段的 class:
public class TestTimestamp {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
private Timestamp createdOn;
// getter and setter
}
在你的 中,你告诉你正在使用 JDK 8,所以我在同一版本中进行了测试(如果你使用的是另一个版本 (JDK <= 7) ,检查底部的 "Not Java 8" 部分)。首先,我创建了一个 Timestamp
对应于 2017 年 9 月 13 日th,UTC:
上午 10 点
TestTimestamp value = new TestTimestamp();
// timestamp corresponds to 2017-09-13T10:00:00 UTC
value.setCreatedOn(Timestamp.from(Instant.parse("2017-09-13T10:00:00Z")));
然后我用Jackson的连载了ObjectMapper
:
ObjectMapper om = new ObjectMapper();
String s = om.writeValueAsString(value);
结果String
是:
{"createdOn":"2017-09-13 10:00"}
请注意,在生成的 JSON 中,输出采用 UTC(上午 10 点)。如果我反序列化这个 JSON:
value = om.readValue(s, TestTimestamp.class);
System.out.println(value.getCreatedOn());
这将打印:
2017-09-13 15:30:00.0
这是因为 Timestamp::toString()
方法(在 System.out.println
中隐式调用)在 JVM 默认时区中打印时间戳。在这种情况下,默认为 Asia/Calcutta
,并且 10 AM in UTC 与 15:30 in Calcutta 相同,所以产生了上面的输出。
正如我在 中解释的那样,Timestamp
对象没有任何时区信息。它只有自 unix 纪元以来的纳秒数(1970-01-01T00:00Z
或 "January 1st 1970 at midnight in UTC")。
使用上面的示例,如果您看到 value.getCreatedOn().getTime()
的值,您会看到它是 1505296800000
- 这是自纪元以来的毫秒数(要获得纳秒精度,有方法 getNanos()
).
此毫秒值对应于 UTC 的上午 10 点、圣保罗的上午 7 点、伦敦的上午 11 点、东京的晚上 7 点、加尔各答的 15:30 等等。你不要在区域之间转换Timestamp
,因为毫秒值在世界各地都是一样的。
不过,您可以更改的是此值的表示(特定时区中相应的date/time)。
在 Jackson 中,您可以创建自定义序列化器和反序列化器(通过扩展 com.fasterxml.jackson.databind.JsonSerializer
和 com.fasterxml.jackson.databind.JsonDeserializer
),这样您就可以更好地控制格式化和解析日期的方式。
首先,我创建了一个序列化程序,将 Timestamp
格式化为 JVM 默认时区:
public class TimestampSerializer extends JsonSerializer<Timestamp> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@Override
public void serialize(Timestamp value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
// get the timestmap in the default timezone
ZonedDateTime z = value.toInstant().atZone(ZoneId.systemDefault());
String str = fmt.format(z);
gen.writeString(str);
}
}
然后我创建一个反序列化程序,它读取 JVM 默认时区中的日期并创建 Timestamp
:
public class TimestampDeserializer extends JsonDeserializer<Timestamp> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@Override
public Timestamp deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// parse to a LocalDateTime
LocalDateTime dt = LocalDateTime.parse(jsonParser.getText(), fmt);
// the date/time is in the default timezone
return Timestamp.from(dt.atZone(ZoneId.systemDefault()).toInstant());
}
}
我还更改字段以使用这些自定义 classes:
// remove JsonFormat annotation
@JsonSerialize(using = TimestampSerializer.class)
@JsonDeserialize(using = TimestampDeserializer.class)
private Timestamp createdOn;
现在,当使用上面相同的 Timestamp
时(对应于 2017-09-13T10:00:00Z
)。序列化它产生:
{"createdOn":"2017-09-13 15:30"}
请注意,现在输出对应于 JVM 默认时区中的本地时间(Asia/Calcutta
中的15:30)。
反序列化此 JSON 时,我得到相同的 Timestamp
(对应于 UTC 上午 10 点)。
此代码使用 JVM 默认时区,但 ,因此最好始终明确说明您使用的是哪个时区。
API 使用 IANA timezones names(格式总是 Region/City
,如 Asia/Calcutta
或 Europe/Berlin
),因此您可以使用 ZoneId.of("Asia/Calcutta")
。
避免使用 3 个字母的缩写(如 IST
或 PST
),因为它们是 ambiguous and not standard.
您可以通过调用 ZoneId.getAvailableZoneIds()
.
获取可用时区列表(并选择最适合您的系统的时区)
如果您想更改输出以对应另一个时区,只需将 ZoneId.systemDefault()
更改为您想要的时区。请记住,必须使用同一个区域进行序列化和反序列化,否则你会得到错误的结果。
不是Java8?
如果您使用 Java <= 7,您可以使用 ThreeTen Backport,这是 Java 8 的新 date/time class 的一个很好的反向移植es.
唯一的区别是包名称(在 Java 8 中是 java.time
,在 ThreeTen Backport 中是 org.threeten.bp
),但是 classes 和方法 名字相同
还有一个区别:只有在 Java 8 中 Timestamp
class 有方法 toInstant()
和 from()
,所以你需要使用 org.threeten.bp.DateTimeUtils
class 进行转换:
public class TimestampSerializer extends JsonSerializer<Timestamp> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@Override
public void serialize(Timestamp value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
// get the timestmap in the default timezone
ZonedDateTime z = DateTimeUtils.toInstant(value).atZone(ZoneId.systemDefault());
String str = fmt.format(z);
gen.writeString(str);
}
}
public class TimestampDeserializer extends JsonDeserializer<Timestamp> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@Override
public Timestamp deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// parse to a LocalDateTime
LocalDateTime dt = LocalDateTime.parse(jsonParser.getText(), fmt);
// the date/time is in the default timezone
return DateTimeUtils.toSqlTimestamp(dt.atZone(ZoneId.systemDefault()).toInstant());
}
}
我的 Java Bean 中有一个字段,如下所示,其中 @JsonFormat
是一个 jackson 注释:
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm")
private Timestamp createdOn;
当我 return Timestamp
时,它总是在我的 JSON 响应中转换为 UTC 时间。让我们说一下格式化我的 Timestamp ,我得到 2017-09-13 15:30
但我的响应被 returned 为 2017-09-13 10:00
.
我知道这是因为 Jackson 注释默认采用系统时区并将时间戳转换为 UTC。 (在我的例子中,服务器属于 Asia/Calcutta
时区,因此偏移量为 +05:30
并且 Jackson 映射器减去 5 小时 30 分钟以将时间戳转换为 UTC)
有没有办法按原样 return 时间戳,即 2017-09-13 15:30
而不是 2017-09-13 10:00
?
您可以使用 timeZone
来明确指定您想要的时区:
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm", timezone="Asia/Calcutta")
private Timestamp createdOn;
我在这里做了一个测试,将 JVM 默认时区更改为 Asia/Calcutta
并创建一个带有 java.sql.Timestamp
字段的 class:
public class TestTimestamp {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
private Timestamp createdOn;
// getter and setter
}
在你的 Timestamp
对应于 2017 年 9 月 13 日th,UTC:
TestTimestamp value = new TestTimestamp();
// timestamp corresponds to 2017-09-13T10:00:00 UTC
value.setCreatedOn(Timestamp.from(Instant.parse("2017-09-13T10:00:00Z")));
然后我用Jackson的连载了ObjectMapper
:
ObjectMapper om = new ObjectMapper();
String s = om.writeValueAsString(value);
结果String
是:
{"createdOn":"2017-09-13 10:00"}
请注意,在生成的 JSON 中,输出采用 UTC(上午 10 点)。如果我反序列化这个 JSON:
value = om.readValue(s, TestTimestamp.class);
System.out.println(value.getCreatedOn());
这将打印:
2017-09-13 15:30:00.0
这是因为 Timestamp::toString()
方法(在 System.out.println
中隐式调用)在 JVM 默认时区中打印时间戳。在这种情况下,默认为 Asia/Calcutta
,并且 10 AM in UTC 与 15:30 in Calcutta 相同,所以产生了上面的输出。
正如我在 Timestamp
对象没有任何时区信息。它只有自 unix 纪元以来的纳秒数(1970-01-01T00:00Z
或 "January 1st 1970 at midnight in UTC")。
使用上面的示例,如果您看到 value.getCreatedOn().getTime()
的值,您会看到它是 1505296800000
- 这是自纪元以来的毫秒数(要获得纳秒精度,有方法 getNanos()
).
此毫秒值对应于 UTC 的上午 10 点、圣保罗的上午 7 点、伦敦的上午 11 点、东京的晚上 7 点、加尔各答的 15:30 等等。你不要在区域之间转换Timestamp
,因为毫秒值在世界各地都是一样的。
不过,您可以更改的是此值的表示(特定时区中相应的date/time)。
在 Jackson 中,您可以创建自定义序列化器和反序列化器(通过扩展 com.fasterxml.jackson.databind.JsonSerializer
和 com.fasterxml.jackson.databind.JsonDeserializer
),这样您就可以更好地控制格式化和解析日期的方式。
首先,我创建了一个序列化程序,将 Timestamp
格式化为 JVM 默认时区:
public class TimestampSerializer extends JsonSerializer<Timestamp> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@Override
public void serialize(Timestamp value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
// get the timestmap in the default timezone
ZonedDateTime z = value.toInstant().atZone(ZoneId.systemDefault());
String str = fmt.format(z);
gen.writeString(str);
}
}
然后我创建一个反序列化程序,它读取 JVM 默认时区中的日期并创建 Timestamp
:
public class TimestampDeserializer extends JsonDeserializer<Timestamp> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@Override
public Timestamp deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// parse to a LocalDateTime
LocalDateTime dt = LocalDateTime.parse(jsonParser.getText(), fmt);
// the date/time is in the default timezone
return Timestamp.from(dt.atZone(ZoneId.systemDefault()).toInstant());
}
}
我还更改字段以使用这些自定义 classes:
// remove JsonFormat annotation
@JsonSerialize(using = TimestampSerializer.class)
@JsonDeserialize(using = TimestampDeserializer.class)
private Timestamp createdOn;
现在,当使用上面相同的 Timestamp
时(对应于 2017-09-13T10:00:00Z
)。序列化它产生:
{"createdOn":"2017-09-13 15:30"}
请注意,现在输出对应于 JVM 默认时区中的本地时间(Asia/Calcutta
中的15:30)。
反序列化此 JSON 时,我得到相同的 Timestamp
(对应于 UTC 上午 10 点)。
此代码使用 JVM 默认时区,但
API 使用 IANA timezones names(格式总是 Region/City
,如 Asia/Calcutta
或 Europe/Berlin
),因此您可以使用 ZoneId.of("Asia/Calcutta")
。
避免使用 3 个字母的缩写(如 IST
或 PST
),因为它们是 ambiguous and not standard.
您可以通过调用 ZoneId.getAvailableZoneIds()
.
如果您想更改输出以对应另一个时区,只需将 ZoneId.systemDefault()
更改为您想要的时区。请记住,必须使用同一个区域进行序列化和反序列化,否则你会得到错误的结果。
不是Java8?
如果您使用 Java <= 7,您可以使用 ThreeTen Backport,这是 Java 8 的新 date/time class 的一个很好的反向移植es.
唯一的区别是包名称(在 Java 8 中是 java.time
,在 ThreeTen Backport 中是 org.threeten.bp
),但是 classes 和方法 名字相同
还有一个区别:只有在 Java 8 中 Timestamp
class 有方法 toInstant()
和 from()
,所以你需要使用 org.threeten.bp.DateTimeUtils
class 进行转换:
public class TimestampSerializer extends JsonSerializer<Timestamp> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@Override
public void serialize(Timestamp value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
// get the timestmap in the default timezone
ZonedDateTime z = DateTimeUtils.toInstant(value).atZone(ZoneId.systemDefault());
String str = fmt.format(z);
gen.writeString(str);
}
}
public class TimestampDeserializer extends JsonDeserializer<Timestamp> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@Override
public Timestamp deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// parse to a LocalDateTime
LocalDateTime dt = LocalDateTime.parse(jsonParser.getText(), fmt);
// the date/time is in the default timezone
return DateTimeUtils.toSqlTimestamp(dt.atZone(ZoneId.systemDefault()).toInstant());
}
}