Format Java 8 LocalDate Jackson

138

Untuk java.util.Date ketika saya melakukannya

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")  
  private Date dateOfBirth;

maka dalam permintaan JSON ketika saya mengirim

{ {"dateOfBirth":"01/01/2000"} }  

berhasil.

Bagaimana saya harus melakukan ini untuk bidang LocalDate Java 8 ??

Saya sudah mencoba

@JsonDeserialize(using = LocalDateDeserializer.class)  
@JsonSerialize(using = LocalDateSerializer.class)  
private LocalDate dateOfBirth;  

Itu tidak berhasil.

Dapatkah seseorang tolong beri tahu saya apa cara yang benar untuk melakukan ini ..

Di bawah ini adalah dependensi

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>jaxrs-api</artifactId>
     <version>3.0.9.Final</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.jaxrs</groupId>
    <artifactId>jackson-jaxrs-json-provider</artifactId>
    <version>2.4.2</version>
</dependency>
<dependency>
    <groupId>com.wordnik</groupId>
    <artifactId>swagger-annotations</artifactId>
    <version>1.3.10</version>
</dependency>
TUSUKAN
sumber

Jawaban:

106

Saya tidak pernah bisa membuat ini berfungsi sederhana menggunakan anotasi. Untuk membuatnya berfungsi, saya membuat ContextResolveruntuk ObjectMapper, lalu saya menambahkan JSR310Module( pembaruan: sekarang sebagai JavaTimeModulegantinya ), bersama dengan satu peringatan lagi, yang merupakan kebutuhan untuk mengatur write-date-as-timestamp menjadi false. Lihat lebih lanjut di dokumentasi untuk modul JSR310 . Ini adalah contoh dari apa yang saya gunakan.

Ketergantungan

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.4.0</version>
</dependency>

Catatan: Satu masalah yang saya hadapi dengan ini adalah bahwa jackson-annotationversi ditarik oleh ketergantungan lain, menggunakan versi 2.3.2, yang membatalkan 2.4 yang diperlukan oleh jsr310. Apa yang terjadi adalah saya mendapat NoClassDefFound untuk ObjectIdResolver, yang merupakan kelas 2.4. Jadi saya hanya perlu berbaris versi ketergantungan yang disertakan

ContextResolver

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {  
    private final ObjectMapper MAPPER;

    public ObjectMapperContextResolver() {
        MAPPER = new ObjectMapper();
        // Now you should use JavaTimeModule instead
        MAPPER.registerModule(new JSR310Module());
        MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return MAPPER;
    }  
}

Kelas sumber daya

@Path("person")
public class LocalDateResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getPerson() {
        Person person = new Person();
        person.birthDate = LocalDate.now();
        return Response.ok(person).build();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createPerson(Person person) {
        return Response.ok(
                DateTimeFormatter.ISO_DATE.format(person.birthDate)).build();
    }

    public static class Person {
        public LocalDate birthDate;
    }
}

Uji

curl -v http://localhost:8080/api/person
Hasil: {"birthDate":"2015-03-01"}

curl -v -POST -H "Content-Type:application/json" -d "{\"birthDate\":\"2015-03-01\"}" http://localhost:8080/api/person
Hasil: 2015-03-01


Lihat juga di sini untuk solusi JAXB.

MEMPERBARUI

Itu JSR310Modulesudah ditinggalkan pada versi 2.7 dari Jackson. Sebagai gantinya, Anda harus mendaftarkan modul JavaTimeModule. Masih ketergantungan yang sama.

Paul Samsotha
sumber
1
Hai Peeskillet, bidang tanggal lahir, sedang dihasilkan sebagai "tanggal lahir": {"tahun": 0, "bulan": "Bulan", "dayOfMonth": 0, "dayOfWeek": "DayOfWeek", "era": {" value ": 0}," dayOfYear ": 0," leapYear ": false," monthValue ": 0," chronology ": {" id ":" "," calendarType ":" "}} bagaimana saya bisa membuatnya hanya sebagai "birthDate" ???
JAB
Periksa ContextResolver dipanggil. Tambahkan pernyataan cetak dalam getContextmetode ini. Jika metode ini dipanggil, saya tidak melihat alasan untuk ini tidak berhasil. Jika tidak dipanggil, maka itu mungkin sesuatu yang perlu diperbaiki dengan konfigurasi aplikasi. Untuk itu saya perlu melihat lebih dari apa yang Anda berikan. Seperti versi Resteasy, dependensi, konfigurasi aplikasi baik web.xml atau subkelas Aplikasi. Pada dasarnya cukup untuk mereproduksi masalah
Paul Samsotha
ContextResolver tidak disebut Peeskillet. Saya mendaftarkannya kembali di web.xml sebagai <context-param> <param-name> resteasy.resources </param-name> <param-value> com.bac.ObjectMapperContextResolver </param-value> </context-param> pertanyaan yang diperbarui untuk dependensi yang saya gunakan
JAB
Kesombongan tampaknya menjadi masalah. Saya akan mengatakan untuk menonaktifkannya tetapi melihat dari pertanyaan ini ada masalah yang telah diajukan, dengan konflik antara ObjectMapper Swagger dan mencoba untuk menggunakan Anda sendiri. Anda dapat mencoba dan menonaktifkannya, dan di ContextResolver, atur semua konfigurasi ke ObjectMapperseperti yang dilakukan oleh angkuh (Anda dapat melihat tautan dalam pertanyaan). Saya tidak tahu karena saya tidak banyak bekerja dengan angkuh. Tapi saya pikir kesombongan adalah masalah utama, mengapa contextresolver tidak dipanggil.
Paul Samsotha
Setelah pengujian lebih lanjut, anotasi tidak berfungsi. Sekalipun Kita harus menggunakan Swagger ObjectMapper, maka sudah mengkonfigurasi prangko waktu sebagai salah bagi kita. Jadi ini seharusnya berhasil. Untuk bantuan yang lebih baik, saya sangat menyarankan Anda memberikan MCVE yang menunjukkan masalah.
Paul Samsotha
95

@JsonSerialize dan @JsonDeserialize bekerja dengan baik untuk saya. Mereka menghilangkan kebutuhan untuk mengimpor modul jsr310 tambahan:

@JsonDeserialize(using = LocalDateDeserializer.class)  
@JsonSerialize(using = LocalDateSerializer.class)  
private LocalDate dateOfBirth;

Deserializer:

public class LocalDateDeserializer extends StdDeserializer<LocalDate> {

    private static final long serialVersionUID = 1L;

    protected LocalDateDeserializer() {
        super(LocalDate.class);
    }


    @Override
    public LocalDate deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        return LocalDate.parse(jp.readValueAs(String.class));
    }

}

Serializer:

public class LocalDateSerializer extends StdSerializer<LocalDate> {

    private static final long serialVersionUID = 1L;

    public LocalDateSerializer(){
        super(LocalDate.class);
    }

    @Override
    public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider sp) throws IOException, JsonProcessingException {
        gen.writeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
    }
}
Christopher Yang
sumber
2
Ini jawaban terbaik untukku. Terima kasih!
Romeo Jr Maranan
7
Kelas-kelas tersebut termasuk dalam jackson-datatype-jsr310. Tidak perlu menentukannya secara manual di proyek Anda.
NeuroXc
1
Solusi ini berhasil untuk saya, dengan menggunakan serializers di jackson-datatype-jsr310.
dave
2
Ini harus menjadi jawaban terbaik yang baru.
Parameter Dokter
1
Jika Anda menggunakan serializers dan deserializers di jackson-datatype-jsr310, lebih baik tambahkan @JsonFormat (bentuk = JsonFormat.Shape.STRING) ke bidang Anda. Tanpa format ini, nilainya akan menjadi serial [tahun, bulan, hari], meskipun deserialisasi akan berfungsi.
Jian Chen
74
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

bekerja dengan baik untuk saya.

欧阳 世雄
sumber
2
new com.fasterxml.jackson.datatype.jsr310.JSR310Module()untuk versi 2.5.4 dari Jackson. Kelas JavaTimeModule tidak ada di versi ini.
user3774109
Jawaban ini juga berfungsi untuk LocalDateTime(jackson 2.9.5). 1 ketergantungan tambahan diperlukan, jadi build.sbt saya terlihat seperti:"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.5", "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.5"
ruhong
2
Ini seharusnya memiliki lebih banyak suara. Sederhana dan efektif.
mkasberg
Bekerja dengan sempurna! Terima kasih.
MAD-HAX
Ini mengarahkan saya ke arah yang benar, Terima kasih! Saya akan menambahkan bahwa di musim semi-boot semua yang perlu Anda lakukan adalah menambahkan yang berikut ke application.properties: spring.jackson.serialization.write-Date-as-timestamps = false
Joost Lambregts
46

Di aplikasi web Spring Boot, dengan versi Jackson dan JSR 310 "2.8.5"

compile "com.fasterxml.jackson.core:jackson-databind:2.8.5"
runtime "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.5"

@JsonFormatKarya - karya:

import com.fasterxml.jackson.annotation.JsonFormat;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate birthDate;
Tsolak Barseghyan
sumber
2
Apakah ini berfungsi untuk deserialisasi? atau hanya serialisasi? Tidak berhasil dengan deserialisasi
rewolf
7
Saya harus secara eksplisit mendeklarasikan deserializer@JsonDeserialize(using= LocalDateDeserializer.class)
rewolf
1
sejauh ini yang paling sederhana!
Antonio
@JsonFormathanya untuk mengubah format data output. stackoverflow.com/a/53251526/816759 bekerja sempurna dengan @JsonFormat, @JsonDeserialize,@JsonSerialize
Baha
Di Spring Boot, setelah Anda menambahkan dependensi JSR310, yang perlu Anda lakukan adalah menambahkan spring.jackson.serialization.write-dates-as-timestamps=falseke Anda application.properties, dan memformatnya yyyy-MM-ddsecara otomatis. Tidak perlu@JsonFormat
esfandia
31

Solusi paling sederhana (yang mendukung deserialisasi dan serialisasi juga) adalah

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
private LocalDate dateOfBirth;

Saat menggunakan dependensi berikut dalam proyek Anda.

Maven

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.9.7</version>
</dependency>
<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-jsr310</artifactId>
   <version>2.9.7</version>
</dependency>

Gradle

compile "com.fasterxml.jackson.core:jackson-databind:2.9.7"
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.7"

Tidak ada implementasi tambahan dari ContextResolver, Serializer atau Deserializer diperlukan.

Paul Wasilewski
sumber
Cemerlang, jauh dan termudah. FYI untuk siapa saja dengan banyak dependensi, saya harus memperbarui beberapa perpustakaan lain yang memasukkan anotasi jackson.
brt
Jawaban ini adalah yang paling dekat saya harus memperbaiki masalah saya. Serialisasi bekerja, tetapi deserialisasi gagal karena pola yang saya gunakan dengan @JsonFormat saya pikir (@JsonFormat (bentuk = JsonFormat.Shape.STRING, pola = "dd-MM-yyyy_HH: mm: SS")
fsakiyama
Jika Anda mengalami deserialisasi yang gagal, kemungkinan besar Anda ObjectMapperbelum JavaTimeModulemendaftar. Jika instance ObjectMapper Anda disediakan dari pegas / MessageConverter framework. Mereka melakukan sihir untuk menghubungkan mereka. Dalam kasus lain, harus registerModulemengaktifkan LocalDateDeserializersecara default untuk semua "LocalDate" di POJO
Dennis C
19

Sejak LocalDateSerializer mengubahnya menjadi "[tahun, bulan, hari]" "(array json) daripada" tahun-bulan-hari "(string json) secara default, dan karena saya tidak ingin memerlukan ObjectMapperpengaturan khusus (Anda dapat make LocalDateSerializermenghasilkan string jika Anda menonaktifkan SerializationFeature.WRITE_DATES_AS_TIMESTAMPStetapi yang memerlukan setup tambahan untuk Anda ObjectMapper), saya menggunakan yang berikut:

impor:

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;

kode:

// generates "yyyy-MM-dd" output
@JsonSerialize(using = ToStringSerializer.class)
// handles "yyyy-MM-dd" input just fine (note: "yyyy-M-d" format will not work)
@JsonDeserialize(using = LocalDateDeserializer.class)
private LocalDate localDate;

Dan sekarang saya bisa menggunakan new ObjectMapper()untuk membaca dan menulis objek saya tanpa pengaturan khusus.

Manusia bayangan
sumber
3
Satu hal yang ingin saya tambahkan adalah melewati tanggal karena "2018-12-07"alih-alih "2018-12-7"Anda akan mendapatkan kesalahan.
Kid101
1
Benar, ini bekerja dengan yyyy-MM-ddformat (2 digit bulan dan hari), bukan format yyyy-M-d(1 digit bulan atau hari).
Shadow Man
8

Hanya pembaruan dari jawaban Christopher.

Sejak versi 2.6.0

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9.0</version>
</dependency>

Gunakan JavaTimeModule alih-alih JSR310Module (usang).

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {  
    private final ObjectMapper MAPPER;

    public ObjectMapperContextResolver() {
        MAPPER = new ObjectMapper();
        MAPPER.registerModule(new JavaTimeModule());
        MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return MAPPER;
    }  
}

Menurut dokumentasi , JavaTimeModule baru menggunakan pengaturan standar yang sama untuk default ke serialisasi yang TIDAK menggunakan Id Zona Waktu, dan sebaliknya hanya menggunakan offset Timezone yang sesuai dengan ISO-8601.

Perilaku dapat diubah menggunakan SerializationFeature.WRITE_DATES_WITH_ZONE_ID

bdzzaid
sumber
Ini membantu saya. Dalam kasus saya, saya perlu menambahkan MAPPER.registerModule(new JavaTimeModule());baris. Itu membiarkan saya memformat objek LocalDate sebagai format "2020-02-20". Saya tidak membutuhkan MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
sambungan
6

Anotasi berikut bekerja dengan baik untuk saya.

Tidak diperlukan dependensi tambahan.

    @JsonProperty("created_at")
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime createdAt;
KayV
sumber
5
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createdDate;
slisnychyi
sumber
4

https://stackoverflow.com/a/53251526/1282532 adalah cara paling sederhana untuk membuat serialisasi / deserialize properti. Saya memiliki dua keprihatinan tentang pendekatan ini - sampai beberapa titik pelanggaran prinsip KERING dan penggandengan tinggi antara pojo dan mapper.

public class Trade {
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate tradeDate;
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate maturityDate;
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate entryDate;
}

Jika Anda memiliki POJO dengan beberapa bidang LocalDate lebih baik untuk mengkonfigurasi mapper daripada POJO. Ini bisa sesederhana https://stackoverflow.com/a/35062824/1282532 jika Anda menggunakan nilai ISO-8601 ("2019-01-31")

Jika Anda perlu menangani format khusus, kodenya akan seperti ini:

ObjectMapper mapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyyMMdd")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyyMMdd")));
mapper.registerModule(javaTimeModule);

Logikanya ditulis sekali saja, dapat digunakan kembali untuk beberapa POJO

Pavel
sumber
3

Paling sederhana dan terpendek sejauh ini:

@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate localDate;

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;

tidak diperlukan ketergantungan dengan booting Spring> = 2.2+

Anil Bhaskar
sumber
1

Pada tahun 2020 dan Jackson 2.10.1 tidak perlu kode khusus, hanya masalah memberi tahu Jackson apa yang Anda inginkan:

ObjectMapper objectMapper = new ObjectMapper();

// Register module that knows how to serialize java.time objects
// Provided by jackson-datatype-jsr310
objectMapper.registerModule(new JavaTimeModule());

// Ask Jackson to serialize dates as String (ISO-8601 by default)
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

Ini telah disebutkan dalam jawaban ini , saya menambahkan tes unit memverifikasi fungsi:

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.Data;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class LocalDateSerializationTest {

    @Data
    static class TestBean {
        // Accept default ISO-8601 format
        LocalDate birthDate;
        // Use custom format
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")
        LocalDate birthDateWithCustomFormat;
    }

    @Test
    void serializeDeserializeTest() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();

        // Register module that knows how to serialize java.time objects
        objectMapper.registerModule(new JavaTimeModule());

        // Ask Jackson to serialize dates as String (ISO-8601 by default)
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        // The JSON string after serialization
        String json = "{\"birthDate\":\"2000-01-02\",\"birthDateWithCustomFormat\":\"03/02/2001\"}";

        // The object after deserialization
        TestBean object = new TestBean();
        object.setBirthDate(LocalDate.of(2000, 1, 2));
        object.setBirthDateWithCustomFormat(LocalDate.of(2001, 2, 3));

        // Assert serialization
        assertEquals(json, objectMapper.writeValueAsString(object));

        // Assert deserialization
        assertEquals(object, objectMapper.readValue(json, TestBean.class));
    }
}

TestBean menggunakan Lombok untuk menghasilkan boilerplate untuk kacang.

Bogdan Calmac
sumber
0

Di kelas konfigurasi, tentukan kelas LocalDateSerializer dan LocalDateDeserializer dan daftarkan mereka ke ObjectMapper melalui JavaTimeModule seperti di bawah ini:

@Configuration
public class AppConfig
{
@Bean
    public ObjectMapper objectMapper()
    {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(Include.NON_EMPTY);
        //other mapper configs
        // Customize de-serialization


        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer());
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer());
        mapper.registerModule(javaTimeModule);

        return mapper;
    }

    public class LocalDateSerializer extends JsonSerializer<LocalDate> {
        @Override
        public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeString(value.format(Constant.DATE_TIME_FORMATTER));
        }
    }

    public class LocalDateDeserializer extends JsonDeserializer<LocalDate> {

        @Override
        public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            return LocalDate.parse(p.getValueAsString(), Constant.DATE_TIME_FORMATTER);
        }
    }
}
nanosoft
sumber
0

Jika permintaan Anda mengandung objek seperti ini:

{
    "year": 1900,
    "month": 1,
    "day": 20
}

Maka Anda dapat menggunakan:

data class DateObject(
    val day: Int,
    val month: Int,
    val year: Int
)
class LocalDateConverter : StdConverter<DateObject, LocalDate>() {
    override fun convert(value: DateObject): LocalDate {
        return value.run { LocalDate.of(year, month, day) }
    }
}

Di atas lapangan:

@JsonDeserialize(converter = LocalDateConverter::class)
val dateOfBirth: LocalDate

Kodenya ada di Kotlin tetapi tentu saja ini juga bisa digunakan untuk Java.

mowwwalker
sumber