Format tanggal Pemetaan ke JSON Jackson

154

Saya memiliki format Tanggal yang berasal dari API seperti ini:

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

Yaitu YYYY-DD-MM HH: MM am / pm GMT timestamp. Saya memetakan nilai ini ke variabel Tanggal di POJO. Jelas, ini menunjukkan kesalahan konversi.

Saya ingin tahu 2 hal:

  1. Apa format yang perlu saya gunakan untuk melakukan konversi dengan Jackson? Apakah Date jenis bidang yang baik untuk ini?
  2. Secara umum, apakah ada cara untuk memproses variabel sebelum dipetakan ke anggota Object oleh Jackson? Sesuatu seperti, mengubah format, perhitungan, dll.
Kevin Rave
sumber
Ini adalah contoh yang sangat bagus, tempatkan anotasi pada bidang kelas: java.dzone.com/articles/how-serialize-javautildate
digz6666
Sekarang mereka memiliki halaman wiki untuk penanganan tanggal: wiki.fasterxml.com/JacksonFAQDateHandling
Sutra
Saat ini Anda tidak boleh lagi menggunakan Datekelas. java.time, API tanggal dan waktu Jawa modern, telah menggantinya hampir 5 tahun yang lalu. Gunakan dan FasterXML / jackson-modules-java8 .
Ole VV

Jawaban:

124

Apa format yang perlu saya gunakan untuk melakukan konversi dengan Jackson? Apakah Date jenis bidang yang baik untuk ini?

Dateadalah tipe bidang halus untuk ini. Anda dapat membuat JSON parse-mampu dengan cukup mudah dengan menggunakan ObjectMapper.setDateFormat:

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

Secara umum, apakah ada cara untuk memproses variabel sebelum dipetakan ke anggota Object oleh Jackson? Sesuatu seperti, mengubah format, perhitungan, dll.

Iya. Anda memiliki beberapa opsi, termasuk menerapkan kebiasaan JsonDeserializer, misalnya memperluas JsonDeserializer<Date>. Ini awal yang bagus.

pb2q
sumber
2
Format 12 jam lebih baik jika formatnya juga mencakup penetapan AM / PM: DateFormat df = new SimpleDateFormat ("yyyy-MM-dd jj: mm a z");
John Scattergood
Mencoba semua solusi ini tetapi tidak dapat menyimpan variabel Tanggal POJO saya ke dalam nilai kunci Peta, juga sebagai Tanggal. Saya ingin ini kemudian instantiate BasicDbObject (MongoDB API) dari Map, dan akibatnya menyimpan variabel dalam koleksi DB MongoDB sebagai Date (bukan sebagai Long atau String). Apakah ini mungkin? Terima kasih
RedEagle
1
Apakah semudah menggunakan Java 8 LocalDateTimeatau ZonedDateTimebukan Date? Karena Datepada dasarnya sudah usang (atau setidaknya banyak metodenya), saya ingin menggunakan alternatif itu.
houcros
Javadocs untuk setSateFormat () mengatakan bahwa panggilan ini membuat ObjectMapper tidak lagi aman. Saya membuat kelas JsonSerializer dan JsonDeserializer.
MiguelMunoz
Karena pertanyaan tidak menyebutkan java.util.Datesecara eksplisit saya ingin menunjukkan bahwa ini tidak berfungsi untuk java.sql.Date.Lihat juga jawaban saya di bawah ini.
Monyet kuningan
329

Sejak Jackson v2.0, Anda dapat menggunakan anotasi @JsonFormat secara langsung pada anggota Object;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;
Olivier Lecrivain
sumber
61
Jika Anda ingin memasukkan zona waktu:@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
realPK
Hai, stoples mana yang memiliki anotasi itu. Saya menggunakan versi jackson-mapper-asl 1.9.10. Saya tidak mengerti
krishna Ram
1
@Ramki: jackson-annotations> = 2.0
Olivier Lecrivain
3
Anotasi ini hanya berfungsi sempurna dalam fase serialisasi tetapi selama deserialisasi, zona waktu dan informasi lokal tidak digunakan sama sekali. Saya telah mencoba zona waktu = "CET" dan zona waktu "Eropa / Budapest" dengan lokal = "hu" tetapi tidak berfungsi dan menyebabkan percakapan waktu yang aneh di Kalender. Hanya serialisasi khusus dengan deserialisasi yang berfungsi untuk saya yang menangani zona waktu sesuai kebutuhan. Berikut ini adalah tutorial sempurna bagaimana Anda perlu menggunakan baeldung.com/jackson-serialize-dates
Miklos Krivan
1
Ini adalah proyek jar terpisah yang disebut Jackson Annotations . Contoh entri pom
bub
52

Tentu saja ada cara otomatis yang disebut serialisasi dan deserialisasi dan Anda dapat mendefinisikannya dengan anotasi tertentu ( @JsonSerialize , @JsonDeserialize ) seperti yang disebutkan oleh pb2q juga.

Anda dapat menggunakan java.util.Date dan java.util.Calendar ... dan mungkin JodaTime juga.

Penjelasan @JsonFormat tidak berfungsi untuk saya seperti yang saya inginkan (telah menyesuaikan zona waktu dengan nilai yang berbeda) selama deserialisasi (serialisasi bekerja dengan sempurna):

@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")

Anda perlu menggunakan serializer khusus dan deserializer khusus alih-alih anotasi @JsonFormat jika Anda ingin hasil yang diprediksi. Saya telah menemukan tutorial dan solusi yang sangat bagus di sini http://www.baeldung.com/jackson-serialize-dates

Ada contoh untuk bidang Tanggal tetapi saya perlu untuk bidang Kalender jadi di sini adalah implementasi saya :

Kelas serializer :

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

Kelas deserializer :

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);
        }
    }
}

dan penggunaan kelas-kelas di atas:

public class CalendarEntry {

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

    // ... additional things ...
}

Dengan menggunakan implementasi ini, pelaksanaan proses serialisasi dan deserialisasi secara berurutan menghasilkan nilai asal.

Hanya menggunakan anotasi @JsonFormat deserialization memberikan hasil yang berbeda saya pikir karena pengaturan default zona waktu internal perpustakaan apa yang tidak dapat Anda ubah dengan parameter anotasi (itu adalah pengalaman saya dengan Jackson library 2.5.3 dan 2.6.3 versi juga).

Miklos Krivan
sumber
4
Saya menerima jawaban saya kemarin. Saya banyak bekerja pada topik ini jadi saya tidak mengerti. Bolehkah saya mendapat umpan balik untuk belajar darinya? Saya akan sangat menghargai beberapa catatan jika terjadi downvote. Dengan cara ini kita dapat belajar lebih banyak dari satu sama lain.
Miklos Krivan
Jawaban yang bagus, terima kasih itu sangat membantu saya! Saran kecil - pertimbangkan untuk menempatkan CustomCalendarSerializer dan CustomCalendarDeserializer sebagai kelas statis di dalam kelas induk terlampir. Saya pikir ini akan membuat kode sedikit lebih baik :)
Stuart
@Stuart - Anda harus menawarkan reorganisasi kode ini sebagai jawaban lain, atau mengusulkan pengeditan. Miklos mungkin tidak punya waktu luang untuk melakukannya.
ocodo
@MiklosKrivan Saya telah menurunkan Anda karena beberapa alasan. Anda harus tahu bahwa SimpleDateFormat bukan threadsafe dan terus terang Anda harus menggunakan pustaka alternatif untuk format tanggal (Joda, Commons-lang FastDateFormat dll). Alasan lainnya adalah pengaturan zona waktu dan bahkan lokal. Jauh lebih disukai menggunakan GMT dalam lingkungan serialisasi dan biarkan klien Anda mengatakan zona waktu mana yang berada atau bahkan melampirkan tz yang disukai sebagai string terpisah. Atur server Anda pada GMT alias UTC. Jackson telah membangun dalam format ISO.
Adam Gent
1
Thx @AdamGent atas tanggapan Anda. Saya mengerti dan menerima saran Anda. Tetapi dalam kasus khusus ini saya hanya ingin fokus bahwa anotasi JsonFormat dengan informasi lokal tidak berfungsi seperti yang kita harapkan. Dan bagaimana bisa diselesaikan.
Miklos Krivan
4

Hanya contoh lengkap untuk aplikasi boot musim semi dengan RFC3339format datetime

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 [email protected] 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"));
    }
}
BaiJiFeiLong
sumber
4

Untuk menambahkan karakter seperti T dan Z di teman kencan Anda

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

keluaran

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

Bekerja untukku. SpringBoot.

 import com.alibaba.fastjson.annotation.JSONField;

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

keluaran:

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

Membangun di atas @ miklov-kriven jawaban yang sangat membantu, saya harap dua hal tambahan ini bermanfaat bagi seseorang:

(1) Saya menemukan ide bagus untuk memasukkan serializer dan de-serializer sebagai kelas statis dalam kelas yang sama. NB, menggunakan ThreadLocal untuk keamanan thread dari SimpleDateFormat.

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) Sebagai alternatif untuk menggunakan @JsonSerialize dan @JsonDeserialize anotasi pada setiap anggota kelas, Anda juga dapat mempertimbangkan mengganti serialisasi default Jackson dengan menerapkan serialisasi khusus pada tingkat aplikasi, yaitu semua anggota kelas tipe Date akan diserialisasi oleh Jackson menggunakan serialisasi khusus ini tanpa anotasi eksplisit di setiap bidang. Jika Anda menggunakan Spring Boot misalnya, salah satu cara untuk melakukannya adalah sebagai berikut:

@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;
    }
}
Stuart
sumber
Saya menurunkan Anda (Saya benci downvoting tapi saya hanya tidak ingin orang menggunakan jawaban Anda). SimpleDateFormat bukan threadsafe. Ini 2016 (Anda menjawab pada 2016). Anda seharusnya tidak menggunakan SimpleDateFormat ketika ada banyak opsi yang lebih cepat dan aman. Bahkan ada penyalahgunaan yang tepat dari apa yang Anda usulkan T / A di sini: stackoverflow.com/questions/25680728/…
Adam Gent
2
@AdGGent, terima kasih untuk menunjukkan ini. Dalam konteks ini, menggunakan Jackson, kelas ObjectMapper aman utas sehingga tidak masalah. Namun, saya mengambil poin Anda bahwa kode dapat disalin dan digunakan dalam konteks aman non-utas. Jadi saya telah mengedit jawaban saya untuk membuat akses ke thread SimpleDateFormat aman. Saya juga mengakui ada alternatif, terutama paket java.time.
Stuart
2

Jika ada yang punya masalah dengan menggunakan format tanggal kustom untuk java.sql.Date, ini adalah solusi paling sederhana:

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

(SO-jawaban ini menyelamatkan saya dari banyak masalah: https://stackoverflow.com/a/35212795/3149048 )

Jackson menggunakan SqlDateSerializer secara default untuk java.sql.Date, tetapi saat ini, serializer ini tidak mempertimbangkan format tanggal, lihat masalah ini: https://github.com/FasterXML/jackson-databind/issues/1407 . Solusinya adalah dengan mendaftarkan serializer yang berbeda untuk java.sql.Date seperti yang ditunjukkan pada contoh kode.

Stephanie
sumber
1

Saya ingin menunjukkan bahwa pengaturan SimpleDateFormatsejenis yang dijelaskan dalam jawaban lain hanya berfungsi untuk java.util.Dateyang saya anggap dimaksud dalam pertanyaan. Tetapi untuk java.sql.Dateformatter tidak berfungsi. Dalam kasus saya, tidak terlalu jelas mengapa formatter tidak bekerja karena dalam model yang harus diserialisasi lapangan sebenarnya adalah java.utl.Datetetapi objek yang sebenarnya berakhir dengan a java.sql.Date. Ini dimungkinkan karena

public class java.sql extends java.util.Date

Jadi ini sebenarnya valid

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

Jadi jika Anda bertanya-tanya mengapa bidang Tanggal Anda tidak diformat dengan benar, pastikan objek tersebut benar-benar a java.util.Date.

Di sini juga disebutkan mengapa penanganan java.sql.Datetidak akan ditambahkan.

Ini kemudian akan merusak perubahan, dan saya pikir itu tidak perlu. Jika kita mulai dari awal, saya akan setuju dengan perubahan, tetapi karena hal-hal tidak begitu banyak.

monyet kuningan
sumber
1
Terima kasih telah menunjukkan satu konsekuensi dari desain buruk dari dua Datekelas yang berbeda ini . Namun, akhir-akhir ini tidak ada yang seharusnya SimpleDateFormat. java.time, API tanggal dan waktu Java modern, telah menggantinya hampir 5 tahun yang lalu. Gunakan dan FasterXML / jackson-modules-java8 .
Ole VV