JSON Jackson ile tarih biçimi Eşleme


154

Ben böyle API gelen bir Tarih biçimi var:

"start_time": "2015-10-1 3:00 PM GMT+1:00"

YYYY-GG-AA SS: DD am / pm GMT zaman damgası. Bu değeri POJO'da bir Date değişkeniyle eşleştiriyorum. Açıkçası, dönüşüm hatası gösteriyor.

2 şey bilmek istiyorum:

  1. Jackson ile dönüşüm gerçekleştirmek için kullanmam gereken biçimlendirme nedir? Tarih bunun için iyi bir alan türü mü?
  2. Genel olarak, değişkenleri Jackson tarafından Object üyelerine eşlenmeden önce işlemenin bir yolu var mı? Biçim, hesaplamalar vb. Gibi bir şey.

Bu gerçekten iyi bir örnek, sınıf alanına not ekleyin
digz6666

Şimdi tarih işleme için bir wiki sayfası var: wiki.fasterxml.com/JacksonFAQDateHandling
Sutra

Bugünlerde artık Datesınıfı kullanmamalısınız . java.time, modern Java tarih ve saat API'sı, yaklaşık 5 yıl önce yerini aldı. Kullanın ve FasterXML / jackson-modules-java8 .
Ole VV

Yanıtlar:


125

Jackson ile dönüşüm gerçekleştirmek için kullanmam gereken biçimlendirme nedir? Tarih bunun için iyi bir alan türü mü?

Datebunun için iyi bir alan türüdür. JSON'u aşağıdakileri kullanarak kolayca ayrıştırılabilir hale getirebilirsiniz ObjectMapper.setDateFormat:

DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
myObjectMapper.setDateFormat(df);

Genel olarak, değişkenleri Jackson tarafından Object üyelerine eşlenmeden önce işlemenin bir yolu var mı? Biçim, hesaplamalar vb. Gibi bir şey.

Evet. JsonDeserializerÖrneğin, genişletme gibi bir özel uygulama da dahil olmak üzere birkaç seçeneğiniz vardır JsonDeserializer<Date>. Bu iyi bir başlangıç.


2
Biçim AM / PM atamasını da içeriyorsa 12 saatlik biçim daha iyidir: DateFormat df = new SimpleDateFormat ("yyyy-AA-gg ss: dd a z");
John Scattergood

Tüm bu çözümleri denedim, ancak POJO'mun Date değişkenini Map anahtar değerine, Date olarak da kaydedemedim. Bu haritadan bir BasicDbObject (MongoDB API) örneklemek ve sonuç olarak değişken olarak bir MongoDB DB koleksiyonunda Tarih (Long veya String olarak değil) depolamak istiyorum. Bu mümkün mü? Teşekkür ederim
RedEagle

1
Java 8'i kullanmak kadar kolay mı LocalDateTimeyoksa ZonedDateTimeyerine Datemi? Yana Datetemelde itiraz edildi (veya yöntemlerin az sayıda at), ben bu alternatifleri kullanmak istiyorum.
houcros

SetSateFormat () için javadocs, bu çağrının ObjectMapper öğesini artık İş Parçacığı için güvenli hale getirmediğini söylüyor. JsonSerializer ve JsonDeserializer sınıflarını oluşturdum.
MiguelMunoz

Soru java.util.Dateaçıkça belirtmediği için bunun işe yaramadığını belirtmek istiyorum. java.sql.Date.Ayrıca aşağıdaki cevabımı da görün.
pirinç maymun

329

Jackson v2.0'dan beri, @JsonFormat ek açıklamasını doğrudan Object üyelerinde kullanabilirsiniz;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;

61
Saat dilimini dahil etmek istiyorsanız:@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
realPK

Merhaba, Hangi kavanozda bu ek açıklama var. Jackson-mapper-asl 1.9.10 sürümünü kullanıyorum. Ben
alamadım

1
@Ramki: jackson ek açıklamaları> = 2.0
Olivier Lecrivain

3
Bu ek açıklama yalnızca serileştirme aşamasında mükemmel çalışır, ancak serileştirme sırasında saat dilimi ve yerel ayar bilgileri hiç kullanılmaz. Yerel dil = "hu" ile saat dilimi = "CET" ve saat dilimi "Avrupa / Budapeşte" denedim ama ne çalışır ve Takvim'de garip zaman konuşma neden olur. Sadece saat dilimine göre özel serileştirme, saat dilimini gerektiği gibi kullanmam için çalıştı. Burada kullanım için gereken ne kadar mükemmel öğretici olduğunu baeldung.com/jackson-serialize-dates
Miklos Krivan

1
Bu Jackson Annotations adlı ayrı bir kavanoz projesidir . Örnek pom girişi
bub

52

Tabii ki serileştirme ve serileştirme adı verilen otomatik bir yol var ve bunu pb2q tarafından da belirtildiği gibi belirli ek açıklamalarla ( @JsonSerialize , @JsonDeserialize ) tanımlayabilirsiniz .

Hem java.util.Date hem de java.util.Calendar ... ve muhtemelen JodaTime'ı kullanabilirsiniz.

@JsonFormat ek açıklamaları , serileştirme sırasında (serileştirme mükemmel çalıştı) istediğim gibi (zaman dilimini farklı değere ayarladı ) benim için çalışmadı:

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "CET")

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Europe/Budapest")

Tahmin edilen sonuç istiyorsanız @JsonFormat ek açıklaması yerine özel serileştirici ve özel serileştirici kullanmanız gerekir. Burada gerçekten iyi bir öğretici ve çözüm buldum http://www.baeldung.com/jackson-serialize-dates

Tarih alanları için örnekler var, ancak Takvim alanları için gerekliyim, işte benim uygulama :

Serileştirici sınıfı:

public class CustomCalendarSerializer extends JsonSerializer<Calendar> {

    public static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    public static final Locale LOCALE_HUNGARIAN = new Locale("hu", "HU");
    public static final TimeZone LOCAL_TIME_ZONE = TimeZone.getTimeZone("Europe/Budapest");

    @Override
    public void serialize(Calendar value, JsonGenerator gen, SerializerProvider arg2)
            throws IOException, JsonProcessingException {
        if (value == null) {
            gen.writeNull();
        } else {
            gen.writeString(FORMATTER.format(value.getTime()));
        }
    }
}

Deserializer sınıfı:

public class CustomCalendarDeserializer extends JsonDeserializer<Calendar> {

    @Override
    public Calendar deserialize(JsonParser jsonparser, DeserializationContext context)
            throws IOException, JsonProcessingException {
        String dateAsString = jsonparser.getText();
        try {
            Date date = CustomCalendarSerializer.FORMATTER.parse(dateAsString);
            Calendar calendar = Calendar.getInstance(
                CustomCalendarSerializer.LOCAL_TIME_ZONE, 
                CustomCalendarSerializer.LOCALE_HUNGARIAN
            );
            calendar.setTime(date);
            return calendar;
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

ve yukarıdaki sınıfların kullanımı :

public class CalendarEntry {

    @JsonSerialize(using = CustomCalendarSerializer.class)
    @JsonDeserialize(using = CustomCalendarDeserializer.class)
    private Calendar calendar;

    // ... additional things ...
}

Bu uygulamayı kullanarak, serileştirme ve serileştirme işleminin yürütülmesi art arda başlangıç ​​değerine yol açar.

Sadece @JsonFormat ek açıklamasını kullanarak, serileştirme farklı sonuç verir, çünkü ek açıklama parametreleriyle değiştiremeyeceğiniz kütüphane iç saat dilimi varsayılan ayarı nedeniyle (bu Jackson kütüphanesi 2.5.3 ve 2.6.3 sürümü ile de benim deneyimimdi).


4
Dün cevabım için aşağı oy aldım. Bu konuda çok çalıştım, anlamıyorum. Öğrenmek için geri bildirim alabilir miyim? Downvote durumunda bazı notları takdir ediyorum. Bu şekilde birbirimizden daha fazla şey öğrenebiliriz.
Miklos Krivan

Harika cevap, teşekkürler gerçekten bana yardımcı oldu! Küçük öneri - CustomCalendarSerializer ve CustomCalendarDeserializer öğelerini ek bir üst sınıf içinde statik sınıflar olarak koymayı düşünün. Bence bu kod biraz daha hoş olur :)
Stuart

@Stuart - kodun bu şekilde yeniden düzenlenmesini başka bir cevap olarak sunmalı veya bir düzenleme önermelisiniz. Miklos'un bunu yapmak için serbest zamanı olmayabilir.
ocodo

@MiklosKrivan Sizi birkaç nedenden dolayı indirdim. SimpleDateFormat'ın güvenli olmadığını bilmelisiniz ve açıkçası tarih biçimlendirmesi için alternatif kütüphaneler kullanmalısınız (Joda, Commons-lang FastDateFormat vb.). Diğer neden, saat diliminin ve hatta yerel ayarın yapılmasıdır. GMT'yi serileştirme ortamında kullanmak ve müşterinizin hangi saat diliminde olduğunu söylemesine veya tercih edilen tz'yi ayrı bir dize olarak eklemesine izin vermek çok daha tercih edilir. Sunucunuzu GMT veya UTC'de olacak şekilde ayarlayın. Jackson, ISO formatını oluşturmuştur.
Adam Gent

1
Görüşleriniz için Thx @AdamGent. Önerilerinizi anlıyorum ve kabul ediyorum. Ancak bu özel durumda, yerel bilgilerle JsonFormat ek açıklamasının beklediğimiz şekilde çalışmadığını vurgulamak istedim. Ve nasıl çözülebilir.
Miklos Krivan

4

Tarih / saat formatına sahip yay önyükleme uygulaması için tam bir örnekRFC3339

package bj.demo;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;

import java.text.SimpleDateFormat;

/**
 * Created by BaiJiFeiLong@gmail.com at 2018/5/4 10:22
 */
@SpringBootApplication
public class BarApp implements ApplicationListener<ApplicationReadyEvent> {

    public static void main(String[] args) {
        SpringApplication.run(BarApp.class, args);
    }

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
    }
}

4

Tarihinize T ve Z gibi karakterler eklemek için

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
private Date currentTime;

çıktı

{
    "currentTime": "2019-12-11T11:40:49Z"
}

3

Benim için çalışıyorum. SpringBoot.

 import com.alibaba.fastjson.annotation.JSONField;

 @JSONField(format = "yyyy-MM-dd HH:mm:ss")  
 private Date createTime;

çıktı:

{ 
   "createTime": "2019-06-14 13:07:21"
}

2

@ Miklov-kriven'in çok yardımcı cevabına dayanarak, umarım bu iki ek nokta birisine faydalı olur:

(1) Aynı sınıftaki statik iç sınıflar olarak serileştiriciyi ve de-serileştiriciyi dahil etmenin iyi bir fikir olduğunu düşünüyorum. NB, SimpleDateFormat'ın iş parçacığı güvenliği için ThreadLocal kullanarak.

public class DateConverter {

    private static final ThreadLocal<SimpleDateFormat> sdf = 
        ThreadLocal.<SimpleDateFormat>withInitial(
                () -> {return new SimpleDateFormat("yyyy-MM-dd HH:mm a z");});

    public static class Serialize extends JsonSerializer<Date> {
        @Override
        public void serialize(Date value, JsonGenerator jgen SerializerProvider provider) throws Exception {
            if (value == null) {
                jgen.writeNull();
            }
            else {
                jgen.writeString(sdf.get().format(value));
            }
        }
    }

    public static class Deserialize extends JsonDeserializer<Date> {
        @Overrride
        public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws Exception {
            String dateAsString = jp.getText();
            try {
                if (Strings.isNullOrEmpty(dateAsString)) {
                    return null;
                }
                else {
                    return new Date(sdf.get().parse(dateAsString).getTime());
                }
            }
            catch (ParseException pe) {
                throw new RuntimeException(pe);
            }
        }
    }
}

(2) Her bir sınıf üyesinde @JsonSerialize ve @JsonDeserialize ek açıklamalarını kullanmaya alternatif olarak, Uygulama düzeyinde özel serileştirmeyi uygulayarak Jackson'ın varsayılan serileştirmeyi geçersiz kılmayı düşünebilirsiniz, yani Date türündeki tüm sınıf üyeleri Jackson tarafından serileştirilir bu özel serileştirmeyi her alan için açık bir ek açıklama olmadan kullanma. Örneğin Spring Boot kullanıyorsanız bunu yapmanın bir yolu aşağıdaki gibidir:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public Module customModule() {
        SimpleModule module = new SimpleModule();
        module.addSerializer(Date.class, new DateConverter.Serialize());
        module.addDeserializer(Date.class, new Dateconverter.Deserialize());
        return module;
    }
}

Seni küçümsedim (küçümsemekten nefret ediyorum ama insanların cevabını kullanmasını istemiyorum). SimpleDateFormat iş parçacığı için güvenli değildir. 2016 (2016'da cevapladınız). Çok sayıda daha hızlı ve iş parçacığı güvenli seçenek olduğunda SimpleDateFormat kullanmamalısınız. Burada Q / A önerdiklerinizin tam olarak kötüye kullanımı bile var: stackoverflow.com/questions/25680728/…
Adam Gent

2
@AdamGent bunu gösterdiğin için teşekkürler. Bu bağlamda, Jackson kullanarak, ObjectMapper sınıfı iş parçacığı güvenlidir, bu yüzden önemli değil. Ancak, kod kopyalanabilir ve iş parçacığı olmayan güvenli bir bağlamda kullanılabilir olduğunu düşünüyorum. Bu yüzden SimpleDateFormat iş parçacığına erişimi güvenli hale getirmek için cevabımı düzenledim. Ayrıca, özellikle java.time paketi gibi alternatifler olduğunu da kabul ediyorum.
Stuart

2

Herkes java.sql.Date için özel bir dateformat kullanma konusunda sorun yaşıyorsa, bu en basit çözümdür:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(java.sql.Date.class, new DateSerializer());
mapper.registerModule(module);

(Bu SO yanıtı bana çok fazla sorun getirdi: https://stackoverflow.com/a/35212795/3149048 )

Jackson, java.sql.Date için varsayılan olarak SqlDateSerializer'ı kullanır, ancak şu anda bu serileştirici, tarih biçimini dikkate almaz, bu soruna bakın: https://github.com/FasterXML/jackson-databind/issues/1407 . Çözüm, kod örneğinde gösterildiği gibi java.sql.Date için farklı bir serileştiriciyi kaydetmektir.


1

SimpleDateFormatDiğer cevapta tarif edilen bir benzerliğin sadece java.util.Datesoruda kastedildiğini düşündüğüm işe yaradığını belirtmek isterim . Ancak java.sql.Datebiçimlendirici için çalışmıyor. Benim durumumda, biçimlendiricinin neden işe yaramadığı çok açık değildi çünkü serileştirilmesi gereken modelde alan aslında bir a oldu, java.utl.Dateancak gerçek nesne arı oldu java.sql.Date. Bu mümkün çünkü

public class java.sql extends java.util.Date

Yani bu aslında geçerli

java.util.Date date = new java.sql.Date(1542381115815L);

Tarih alanınızın neden doğru biçimlendirilmediğini merak ediyorsanız, nesnenin gerçekten bir olduğundan emin olun java.util.Date.

Burada neden işleme java.sql.Dateeklenmeyeceği de belirtilmektedir .

Bu daha sonra değişimi bozacaktı ve bunun garanti olduğunu sanmıyorum. Sıfırdan başlasaydık değişime katılırdım, ama işler o kadar da değil.


1
İki farklı Datesınıfın bu kötü tasarımının bir sonucunu işaret ettiğiniz için teşekkür ederiz . Bu günlerde hiç kimse olmamalı, ne de SimpleDateFormat. java.time, modern Java tarih ve saat API'sı, yaklaşık 5 yıl önce yerini aldı. Kullanın ve FasterXML / jackson-modules-java8 .
Ole VV
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.