2017-09-11 32 views
2

我在我的Java Bean的一个领域了下方@JsonFormat是杰克逊注释:如何使用Java中的Jackson Annotation按原样反序列化Timestamp?

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm") 
private Timestamp createdOn; 

当我返回Timestamp,它总是得到转换为UTC时间在我的JSON响应。让我们说格式化我的时间戳,我得到2017-09-13 15:30但我的回应是返回为2017-09-13 10:00

我知道这是因为默认情况下杰克逊注解需要System TimeZone并将Timestamp转换为UTC。 (在我的情况下,服务器属于Asia/Calcutta时区,因此偏移量为+05:30,杰克逊映射器减去5小时30分钟将时间戳转换为UTC)

有没有办法按原样返回时间戳,即2017-09-13 15:30而不是2017-09-13 10:00

+0

如果你的时区偏移量为5: 30,为什么在这两个时间之间只有5个小时? 30分钟去哪了? – Andreas

+0

修正了错字! –

回答

2

我在这里做了一个试验,用java.sql.Timestamp场改变JVM默认时区以Asia/Calcutta和创建类:

public class TestTimestamp { 

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm") 
    private Timestamp createdOn; 

    // getter and setter 
} 

在您的other question中,您告诉我正在使用JDK 8,所以我测试了相同的版本(如果您使用的是其他版本(JDK < = 7),请检查“Not Java 8“底部的部分)。首先,我在UTC创建Timestamp对应于9月13日 2017年,上午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"))); 

然后,我与杰克逊的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年9月13日15:30:00.0

这是因为Timestamp::toString()方法(这是在所谓的含蓄System.out.println)在JVM默认时区中打印时间戳。在这种情况下,默认值为Asia/Calcutta,上午10点(UTC)15:30在加尔各答相同,因此上面的输出是生成的。

正如我在my answer to your other question中所解释的,Timestamp对象没有任何时区信息。它自unix时代以来只有几纳秒(1970-01-01T00:00Z“1970年1月1日午夜UTC”)。

使用上面的例子,如果你看到的value.getCreatedOn().getTime()的价值,你会看到它的1505296800000 - 这是毫秒数,因为时代(以获得毫微秒的精度,有方法getNanos())。

这个毫秒值对应UTC的上午10点,圣保罗的上午7点,伦敦的上午11点,东京的下午7点,加尔各答的15点30分等等。您不会在区域之间转换Timestamp,因为世界各地的毫米值都是一样的。

但是,您可以更改的是此值的表示(特定时区中的相应日期/时间)。在杰克逊,您可以创建自定义序列化器和反序列化器(通过扩展com.fasterxml.jackson.databind.JsonSerializercom.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()); 
    } 
} 
我也换领域使用这些定制类:

现在,当使用上面相同的Timestamp(即对应于2017-09-13T10:00:00Z)。序列化它产生:

{ “createdOn”: “2017年9月13日15:30”},现在的输出对应于JVM默认的时区(本地时间15

注意: 30在Asia/Calcutta)。

当反序列化这个JSON时,我得到相同的Timestamp(对应于UTC的上午10点)。


该代码使用JVM默认时区,但它can be changed without notice, even at runtime,所以最好总是让它明确你使用哪一个。

API使用IANA timezones names(总是在格式Region/City,像Asia/CalcuttaEurope/Berlin),这样你就可以使用他们创造ZoneId.of("Asia/Calcutta")。 避免使用3字母缩写(如IST或),因为它们是ambiguous and not standard

通过调用ZoneId.getAvailableZoneIds(),您可以获得可用时区列表(并选择最适合您系统的时区)。

如果要将输出更改为与其他时区相对应,请将ZoneId.systemDefault()更改为所需的区域。请记住,同一区域必须用于序列化和反序列化,否则您将得到错误的结果。


不是Java 8?

如果您使用的是Java < = 7,则可以使用ThreeTen Backport,这是用于Java 8的新日期/时间类的一个很好的后端。

唯一的区别是包名(在Java中8是java.time和ThreeTen反向移植是org.threeten.bp),但类和方法名称是相同的。

还有另一个不同之处:只在Java 8 Timestamp类有方法toInstant()from(),所以你需要使用org.threeten.bp.DateTimeUtils类,使转换:

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()); 
    } 
} 
+1

非常感谢Hugo。我扩展了JsonSerializer和JsonDeserializer类,不仅获得了我想要的东西,而且现在我可以完全控制我的请求/响应格式。那么,这个老学校说'自己动手'永远不会变老...... –

+0

@PrasathGovind不客气,很高兴帮助! –

2

您可以使用timeZone指定要明确的时区:

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm", timezone="Asia/Calcutta") 
private Timestamp createdOn; 
+1

这不是我所期望的。不过谢谢。由于我使用云服务,因此我可以自由更改服务器的位置。因此,当我更改服务器位置时,我无法改变Bean中的时区。 –

+0

@PrasathGovind你尝试过'timezone = JsonFormat.DEFAULT_TIMEZONE'吗? – Andreas

+0

没有。我试过timezone = JsonFormat.DEFAULT_TIMEZONE,timezone =“GMT”和timezone =“UTC”没有任何工作。我试过的所有注释,从Timestamp中扣除5小时30分钟。我想按原样返回而不用减法。 –