serialize / deserialize java 8 java.time dengan Jackson JSON mapper

221

Bagaimana cara menggunakan mapper Jackson JSON dengan Java 8 LocalDateTime?

org.codehaus.jackson.map.JsonMappingException: Tidak dapat membuat instantiate nilai tipe [tipe sederhana, kelas java.time.LocalDateTime] dari JSON String; tidak ada metode String-konstruktor / pabrik tunggal (melalui rantai referensi: MyDTO ["field1"] -> SubDTO ["date"])

Alexander Taylor
sumber
4
Anda yakin ingin memetakan LocalDateTime ke JSon? Sejauh yang saya tahu, JSon tidak memiliki format untuk tanggal, meskipun JavaScript menggunakan ISO-8601. Masalahnya adalah, LocalDateTime tidak memiliki zona waktu ... jadi, jika Anda menggunakan JSON sebagai media untuk mengirim info tanggal / waktu, Anda mungkin mendapat masalah jika klien akan mengartikan kurangnya zona waktu sebagai UTC default (atau miliknya sendiri zona waktu). Jika itu yang ingin Anda lakukan, tentu saja itu baik-baik saja. Tetapi periksa apakah Anda telah mempertimbangkan untuk menggunakan ZonedDateTime sebagai gantinya
arcuri82

Jawaban:

276

Tidak perlu menggunakan serializers / deserializers khusus di sini. Gunakan modul datetime jackson-modules-java8 :

Modul Datatype untuk membuat Jackson mengenali tipe data Java 8 Date & Time API (JSR-310).

Modul ini menambahkan dukungan untuk beberapa kelas:

  • Durasi
  • Instan
  • LocalDateTime
  • Tanggal lokal
  • Waktu lokal
  • Bulan hari
  • OffsetDateTime
  • OffsetTime
  • Titik
  • Tahun
  • Tahun bulan
  • ZonedDateTime
  • ZoneId
  • ZoneOffset
Matt Ball
sumber
59
Oh ya! Saya pikir ini tidak berhasil karena saya tidak menyadari bahwa seseorang harus melakukan salah satu penyesuaian ini ke objek mapper: registerModule(new JSR310Module())atau findAndRegisterModules(). Lihat github.com/FasterXML/jackson-datatype-jsr310 dan di sini adalah bagaimana menyesuaikan objek mapper Anda jika Anda menggunakan kerangka kerja Spring: stackoverflow.com/questions/7854030/…
Alexander Taylor
10
Tidak bekerja dengan yang paling sederhana yang saya coba,OffsetDateTime @Test public void testJacksonOffsetDateTimeDeserializer() throws IOException { ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); String json = "\"2015-10-20T11:00:00-8:30\""; mapper.readValue(json, OffsetDateTime.class); }
Abhijit Sarkar
9
Jika Anda menggunakan kerangka kerja Spring baru-baru ini, Anda tidak perlu lagi menyesuaikan sendiri, seperti juga modul-modul Jackson yang terkenal seperti ini sekarang secara otomatis terdaftar jika terdeteksi pada classpath: spring.io/blog/2014/12/02/…
vorburger
37
JSR310Module sudah agak ketinggalan jaman, alih-alih gunakan JavaTimeModule
Jacob Eckel
17
@MattBall Saya akan menyarankan menambahkan bahwa, selain menggunakan jackson-datatype-jsr310, Anda harus mendaftar JavaTimeModule dengan mapper objek Anda: objectMapper.registerModule(new JavaTimeModule());. Baik pada serialisasi dan deserialisasi.
GrandAdmiral
76

Pembaruan: Meninggalkan jawaban ini karena alasan historis, tetapi saya tidak merekomendasikannya. Silakan lihat jawaban yang diterima di atas.

Beri tahu Jackson untuk memetakan menggunakan kelas serialisasi kustom [de] Anda:

@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime ignoreUntil;

menyediakan kelas khusus:

public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
    @Override
    public void serialize(LocalDateTime arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException {
        arg1.writeString(arg0.toString());
    }
}

public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
    @Override
    public LocalDateTime deserialize(JsonParser arg0, DeserializationContext arg1) throws IOException {
        return LocalDateTime.parse(arg0.getText());
    }
}

fakta acak: jika saya bersarang di atas kelas dan tidak membuatnya statis, pesan kesalahannya aneh: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported

Alexander Taylor
sumber
pada tulisan ini, satu-satunya keuntungan di sini daripada jawaban lain tidak tergantung pada FasterXML, apa pun itu.
Alexander Taylor
4
FasterXML adalah Jackson. fastxml.com github.com/fasterxml/jackson
Matt Ball
4
Oh begitu. begitu banyak entri pom kuno dalam proyek yang sedang saya kerjakan. jadi tidak ada lagi org.codehaus, semuanya com.fasterxml sekarang. Terima kasih!
Alexander Taylor
2
Juga saya tidak dapat menggunakan yang built-in karena saya harus lulus ISO_DATE_TIME formatter. Tidak yakin apakah mungkin untuk melakukannya saat menggunakan JSR310Module.
isaac.hazan
Menggunakan ini dengan Spring saya harus membuat kacang keduanya LocalDateTimeSerializerdanLocalDateTimeDeserializer
BenR
53

Jika Anda menggunakan kelas ObjectMapper dari fastxml, secara default ObjectMapper tidak memahami kelas LocalDateTime, jadi, Anda perlu menambahkan dependensi lain di gradle / maven Anda:

compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.7.3'

Sekarang Anda perlu mendaftarkan dukungan tipe data yang ditawarkan oleh perpustakaan ini ke objek Anda objectmapper, ini dapat dilakukan dengan mengikuti:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();

Sekarang, di jsonString Anda, Anda dapat dengan mudah menempatkan bidang java.LocalDateTime Anda sebagai berikut:

{
    "user_id": 1,
    "score": 9,
    "date_time": "2016-05-28T17:39:44.937"
}

Dengan melakukan semua ini, konversi file Json ke Java Anda akan berfungsi dengan baik, Anda dapat membaca file dengan mengikuti:

objectMapper.readValue(jsonString, new TypeReference<List<User>>() {
            });
greperror
sumber
4
findAndRegisterModules()adalah bagian penting yang hilang bagi saya ketika membangun sebuahObjectMapper
Xaero Degreaz
2
Bagaimana dengan Instan?
powder366
1
Saya juga menambahkan baris ini: objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Janet
ini adalah kode lengkap yang diperlukan: ObjectMapper objectMapper = new ObjectMapper (); objectMapper.findAndRegisterModules (); objectMapper.configure (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
khush
42

Ketergantungan pakar ini akan memecahkan masalah Anda:

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

Satu hal yang saya perjuangkan adalah zona waktu ZonedDateTime diubah menjadi GMT selama deserialisasi. Ternyata, bahwa secara default jackson menggantinya dengan satu dari konteks .. Untuk menjaga zona satu harus menonaktifkan 'fitur' ini

Jackson2ObjectMapperBuilder.json()
    .featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
saya ambil
sumber
5
Terima kasih banyak atas DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE petunjuk.
Andrei Urvantcev
5
Selain menonaktifkan, DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONEsaya juga harus menonaktifkan SerializationFeature.WRITE_DATES_AS_TIMESTAMPSagar semuanya mulai berfungsi sebagaimana mestinya.
Matt Watson
16

Saya memiliki masalah yang sama saat menggunakan boot Spring . Dengan Spring boot 1.5.1.RELEASE yang harus saya lakukan adalah menambahkan ketergantungan:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Witold Kaczurba
sumber
7

Jika Anda menggunakan Jersey maka Anda perlu menambahkan dependensi Maven (jackson-datatype-jsr310) seperti yang disarankan orang lain dan mendaftarkan instance mapper objek Anda seperti:

@Provider
public class JacksonObjectMapper implements ContextResolver<ObjectMapper> {

  final ObjectMapper defaultObjectMapper;

  public JacksonObjectMapper() {
    defaultObjectMapper = createDefaultMapper();
  }

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

  private static ObjectMapper createDefaultMapper() {
    final ObjectMapper mapper = new ObjectMapper();    
    mapper.registerModule(new JavaTimeModule());
    return mapper;
  }
}

Saat mendaftarkan Jackson di sumber daya Anda, Anda perlu menambahkan mapper ini seperti:

final ResourceConfig rc = new ResourceConfig().packages("<your package>");
rc
  .register(JacksonObjectMapper.class)
  .register(JacksonJaxbJsonProvider.class);
Sul Aga
sumber
6

Jika Anda tidak dapat menggunakan jackson-modules-java8untuk alasan apa pun, Anda dapat (membatalkan) membuat serialisasi bidang instan longmenggunakan @JsonIgnoredan @JsonGetter& @JsonSetter:

public class MyBean {

    private Instant time = Instant.now();

    @JsonIgnore
    public Instant getTime() {
        return this.time;
    }

    public void setTime(Instant time) {
        this.time = time;
    }

    @JsonGetter
    private long getEpochTime() {
        return this.time.toEpochMilli();
    }

    @JsonSetter
    private void setEpochTime(long time) {
        this.time = Instant.ofEpochMilli(time);
    }
}

Contoh:

@Test
public void testJsonTime() throws Exception {
    String json = new ObjectMapper().writeValueAsString(new MyBean());
    System.out.println(json);
    MyBean myBean = new ObjectMapper().readValue(json, MyBean.class);
    System.out.println(myBean.getTime());
}

hasil panen

{"epochTime":1506432517242}
2017-09-26T13:28:37.242Z
Udo
sumber
Ini adalah metode yang sangat bersih dan memungkinkan pengguna kelas Anda untuk menggunakan cara apa pun yang mereka inginkan.
Ashhar Hasan
3

Saya menggunakan format waktu ini: "{birthDate": "2018-05-24T13:56:13Z}"untuk deserialize dari json ke java.time.Instant (lihat screenshot)

masukkan deskripsi gambar di sini

Taras Melnyk
sumber
3

Ini hanyalah contoh cara menggunakannya dalam unit test yang saya retas untuk men-debug masalah ini. Bahan utamanya adalah

  • mapper.registerModule(new JavaTimeModule());
  • ketergantungan maven <artifactId>jackson-datatype-jsr310</artifactId>

Kode:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.io.IOException;
import java.io.Serializable;
import java.time.Instant;

class Mumu implements Serializable {
    private Instant from;
    private String text;

    Mumu(Instant from, String text) {
        this.from = from;
        this.text = text;
    }

    public Mumu() {
    }

    public Instant getFrom() {
        return from;
    }

    public String getText() {
        return text;
    }

    @Override
    public String toString() {
        return "Mumu{" +
                "from=" + from +
                ", text='" + text + '\'' +
                '}';
    }
}
public class Scratch {


    @Test
    public void JacksonInstant() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());

        Mumu before = new Mumu(Instant.now(), "before");
        String jsonInString = mapper.writeValueAsString(before);


        System.out.println("-- BEFORE --");
        System.out.println(before);
        System.out.println(jsonInString);

        Mumu after = mapper.readValue(jsonInString, Mumu.class);
        System.out.println("-- AFTER --");
        System.out.println(after);

        Assert.assertEquals(after.toString(), before.toString());
    }

}
Mircea Stanciu
sumber
2

Jika Anda menggunakan boot Spring dan memiliki masalah ini dengan OffsetDateTime maka perlu menggunakan registerModules seperti yang dijawab di atas oleh @greperror (dijawab 28 Mei 16 pada 13:04) tetapi perhatikan bahwa ada satu perbedaan. Ketergantungan yang disebutkan tidak perlu ditambahkan karena saya menduga boot spring sudah memilikinya. Saya mengalami masalah dengan boot Spring dan ini berhasil untuk saya tanpa menambahkan ketergantungan ini.

VC2019
sumber
1

Anda dapat mengatur ini di application.ymlfile Anda untuk menyelesaikan waktu Instan, yang merupakan API Tanggal di java8:

spring.jackson.serialization.write-dates-as-timestamps=false
Janki
sumber
1

Bagi yang menggunakan Spring Boot 2.x

Tidak perlu melakukan hal di atas - Java 8 LocalDateTime adalah serial / de-serial keluar dari kotak. Saya harus melakukan semua hal di atas dalam 1.x, tetapi dengan Boot 2.x, ia berfungsi dengan mulus.

Lihat referensi ini juga format JSON Java 8 LocalDateTime di Spring Boot

HopeKing
sumber
1

Jika ada yang mengalami masalah saat menggunakan di SpringBootsini adalah bagaimana saya memperbaiki masalah tanpa menambahkan ketergantungan baru.

Dalam Spring 2.1.3Jackson mengharapkan string tanggal 2019-05-21T07:37:11.000dalam yyyy-MM-dd HH:mm:ss.SSSformat ini untuk de-serialisasi dalam LocalDateTime. Pastikan string tanggal memisahkan tanggal dan waktu dengan Ttidak dengan space. detik ( ss) dan milidetik ( SSS) dapat dinonaktifkan.

@JsonProperty("last_charge_date")
public LocalDateTime lastChargeDate;
Muhammad Usman
sumber
0

Jika Anda mempertimbangkan untuk menggunakan fastjson, Anda dapat menyelesaikan masalah Anda, catat versinya

 <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.56</version>
 </dependency>
byte mamba
sumber
0

Jika Anda mengalami masalah ini karena GraphQL Java Tools dan mencoba Instantmenyusun Java dari string tanggal, Anda perlu mengatur SchemaParser Anda untuk menggunakan ObjectMapper dengan konfigurasi tertentu:

Di kelas GraphQLSchemaBuilder Anda, suntikkan ObjectMapper dan tambahkan modul ini:

        ObjectMapper objectMapper = 
    new ObjectMapper().registerModule(new JavaTimeModule())
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

dan tambahkan ke opsi:

final SchemaParserOptions options = SchemaParserOptions.newOptions()
            .objectMapperProvider(fieldDefinition -> objectMapper)
            .typeDefinitionFactory(new YourTypeDefinitionFactory())
            .build();

Lihat https://github.com/graphql-java-kickstart/graphql-spring-boot/issues/32

Janac Meena
sumber
0

Jika Anda menggunakan Jackson Serializer , berikut adalah cara menggunakan modul tanggal:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.apache.kafka.common.serialization.Serializer;

public class JacksonSerializer<T> implements Serializer<T> {

    private final ObjectMapper mapper = new ObjectMapper()
            .registerModule(new ParameterNamesModule())
            .registerModule(new Jdk8Module())
            .registerModule(new JavaTimeModule());

    @Override
    public byte[] serialize(String s, T object) {
        try {
            return mapper.writeValueAsBytes(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }
}
edubriguenti
sumber