2017-09-11 35 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分鐘。我想按原樣返回而不用減法。 –