serialize / deserialize java 8 java.time mit Jackson JSON Mapper

221

Wie verwende ich Jackson JSON Mapper mit Java 8 LocalDateTime?

org.codehaus.jackson.map.JsonMappingException: Wert vom Typ [einfacher Typ, Klasse java.time.LocalDateTime] kann nicht aus JSON String instanziiert werden; kein Single-String-Konstruktor / Factory-Methode (über die Referenzkette: MyDTO ["field1"] -> SubDTO ["date"])

Alexander Taylor
quelle
4
Sind Sie sicher, dass Sie JSon eine LocalDateTime zuordnen möchten? Soweit ich weiß, hat JSon kein Format für Datumsangaben, obwohl JavaScript ISO-8601 verwendet. Das Problem ist, dass LocalDateTime keine Zeitzone hat. Wenn Sie also JSON als Medium zum Senden von Datums- / Zeitinformationen verwenden, können Probleme auftreten, wenn der Client das Fehlen einer Zeitzone als Standard-UTC (oder als eigene UTC) interpretiert Zeitzone). Wenn Sie das tun möchten, ist es natürlich in Ordnung. Aber überprüfen Sie einfach, ob Sie in Betracht gezogen haben, stattdessen ZonedDateTime zu verwenden
arcuri82

Antworten:

276

Hier müssen keine benutzerdefinierten Serializer / Deserializer verwendet werden. Verwenden Sie das datetime-Modul von jackson-modules-java8 :

Datentypmodul, mit dem Jackson Java 8 Date & Time API-Datentypen (JSR-310) erkennt.

Dieses Modul bietet Unterstützung für einige Klassen:

  • Dauer
  • Sofortig
  • LocalDateTime
  • LocalDate
  • Ortszeit
  • Monat Tag
  • OffsetDateTime
  • OffsetTime
  • Zeitraum
  • Jahr
  • Jahr Monat
  • ZonedDateTime
  • ZoneId
  • ZoneOffset
Matt Ball
quelle
59
Oh ja! Ich dachte, das funktioniert nicht, weil ich nicht wusste, dass man eine dieser Anpassungen am Mapper-Objekt vornehmen muss: registerModule(new JSR310Module())oder findAndRegisterModules(). Siehe github.com/FasterXML/jackson-datatype-jsr310 und hier erfahren
Alexander Taylor
10
Funktioniert nicht mit dem einfachsten, mit dem ich es versucht habeOffsetDateTime @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
Wenn Sie das aktuelle Spring-Framework verwenden, müssen Sie sich nicht mehr anpassen, da bekannte Jackson-Module wie dieses jetzt automatisch registriert werden, wenn sie im Klassenpfad erkannt werden: spring.io/blog/2014/12/02/…
vorburger
36
JSR310Module ist bereits etwas veraltet, verwenden Sie stattdessen JavaTimeModule
Jacob Eckel
17
@MattBall Ich würde vorschlagen, dass Sie zusätzlich zur Verwendung von jackson-datatype-jsr310 das JavaTimeModule bei Ihrem Objekt-Mapper registrieren müssen : objectMapper.registerModule(new JavaTimeModule());. Sowohl bei der Serialisierung als auch bei der Deserialisierung.
GrandAdmiral
76

Update: Diese Antwort aus historischen Gründen hinterlassen, aber ich empfehle sie nicht. Bitte beachten Sie die oben akzeptierte Antwort.

Weisen Sie Jackson an, mithilfe Ihrer benutzerdefinierten [de] Serialisierungsklassen zuzuordnen:

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

Stellen Sie benutzerdefinierte Klassen bereit:

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

zufällige Tatsache: Wenn ich über Klassen verschachtele und sie nicht statisch mache, ist die Fehlermeldung seltsam: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported

Alexander Taylor
quelle
Zum jetzigen Zeitpunkt hängt der einzige Vorteil gegenüber anderen Antworten nicht von FasterXML ab, was auch immer das ist.
Alexander Taylor
4
FasterXML ist Jackson. schnellerxml.com github.com/fasterxml/jackson
Matt Ball
4
Oh, ich verstehe. So viele alte Pom-Einträge in dem Projekt, an dem ich arbeite. Also kein org.codehaus mehr, jetzt ist alles com.fasterxml. Vielen Dank!
Alexander Taylor
2
Außerdem konnte ich das integrierte nicht verwenden, da ich den Formatierer ISO_DATE_TIME übergeben musste. Ich bin mir nicht sicher, ob dies bei Verwendung des JSR310Module möglich ist.
isaac.hazan
Mit Spring musste ich eine Bohne aus beiden LocalDateTimeSerializerundLocalDateTimeDeserializer
BenR
53

Wenn Sie die ObjectMapper-Klasse von schnellerxml verwenden, versteht ObjectMapper die LocalDateTime-Klasse standardmäßig nicht. Daher müssen Sie Ihrem Gradle / Maven eine weitere Abhängigkeit hinzufügen:

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

Jetzt müssen Sie die von dieser Bibliothek angebotene Datentypunterstützung in Ihrem Objectmapper-Objekt registrieren. Dies kann folgendermaßen erfolgen:

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

Jetzt können Sie in Ihrem jsonString Ihr java.LocalDateTime-Feld wie folgt einfügen:

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

Auf diese Weise funktioniert die Konvertierung Ihrer Json-Datei in Java-Objekte einwandfrei. Sie können die Datei folgendermaßen lesen:

objectMapper.readValue(jsonString, new TypeReference<List<User>>() {
            });
greperror
quelle
4
findAndRegisterModules()war ein kritisches Stück, das mir beim Bau einesObjectMapper
Xaero Degreaz
2
Was ist mit Instant?
Pulver366
1
Ich habe auch diese Zeile hinzugefügt: objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Janet
Dies ist der vollständige Code, der benötigt wird: ObjectMapper objectMapper = new ObjectMapper (); objectMapper.findAndRegisterModules (); objectMapper.configure (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Khush
42

Diese Maven-Abhängigkeit löst Ihr Problem:

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

Eine Sache, mit der ich zu kämpfen hatte, war, dass die Zeitzone von ZonedDateTime während der Deserialisierung auf GMT geändert wurde. Es stellte sich heraus, dass Jackson es standardmäßig durch eines aus dem Kontext ersetzt. Um die Zone beizubehalten, muss diese 'Funktion' deaktiviert werden.

Jackson2ObjectMapperBuilder.json()
    .featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
ich nehme
quelle
5
Vielen Dank für den Hinweis DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE.
Andrei Urvantcev
5
Neben Deaktivierung DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONEich deaktivieren hatte auch SerializationFeature.WRITE_DATES_AS_TIMESTAMPSfür alles anfangen zu arbeiten , wie es sollte.
Matt Watson
16

Ich hatte ein ähnliches Problem bei der Verwendung von Spring Boot . Mit Spring Boot 1.5.1.RELEASE musste ich nur die Abhängigkeit hinzufügen:

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

Wenn Sie Jersey verwenden, müssen Sie die Maven-Abhängigkeit (jackson-datatype-jsr310) wie von den anderen vorgeschlagen hinzufügen und Ihre Objekt-Mapper-Instanz wie folgt registrieren:

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

Wenn Sie Jackson in Ihren Ressourcen registrieren, müssen Sie diesen Mapper wie folgt hinzufügen:

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

Wenn Sie nicht verwenden können , jackson-modules-java8für welchen Gründen auch immer können Sie die (De-) serialisiert werden die Instant - Feld als longmit @JsonIgnoreund @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);
    }
}

Beispiel:

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

ergibt

{"epochTime":1506432517242}
2017-09-26T13:28:37.242Z
Du tust
quelle
Dies ist eine sehr saubere Methode, mit der Benutzer Ihrer Klasse nach Belieben arbeiten können.
Ashhar Hasan
3

Ich benutze dieses Zeitformat: "{birthDate": "2018-05-24T13:56:13Z}"um von json in java.time.Instant zu deserialisieren (siehe Screenshot)

Geben Sie hier die Bildbeschreibung ein

Taras Melnyk
quelle
3

Dies ist nur ein Beispiel für die Verwendung in einem Komponententest, den ich gehackt habe, um dieses Problem zu beheben. Die Hauptzutaten sind

  • mapper.registerModule(new JavaTimeModule());
  • Maven Abhängigkeit von <artifactId>jackson-datatype-jsr310</artifactId>

Code:

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
quelle
2

Wenn Sie Spring Boot verwenden und dieses Problem mit OffsetDateTime haben, müssen Sie die registerModules verwenden, wie oben von @greperror (beantwortet am 28. Mai 16 um 13:04 Uhr) beantwortet. Beachten Sie jedoch, dass es einen Unterschied gibt. Die erwähnte Abhängigkeit muss nicht hinzugefügt werden, da ich vermute, dass Spring Boot sie bereits hat. Ich hatte dieses Problem mit Spring Boot und es funktionierte für mich, ohne diese Abhängigkeit hinzuzufügen.

VC2019
quelle
1

Sie können dies in Ihrer application.ymlDatei festlegen, um die Sofortzeit aufzulösen. Dies ist die Datums-API in Java8:

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

Für diejenigen, die verwenden Spring Boot 2.x verwenden

Es ist nicht erforderlich, die oben genannten Schritte auszuführen. Java 8 LocalDateTime wird standardmäßig serialisiert / de-serialisiert. Ich musste all das in 1.x machen, aber mit Boot 2.x funktioniert es nahtlos.

Siehe auch diese Referenz JSON Java 8 LocalDateTime-Format in Spring Boot

HopeKing
quelle
1

Wenn jemand Probleme bei der Verwendung hat SpringBoot hat, habe ich das Problem behoben, ohne neue Abhängigkeiten hinzuzufügen.

In Spring 2.1.3Jackson wird erwartet, dass Datumszeichenfolgen 2019-05-21T07:37:11.000in diesem yyyy-MM-dd HH:mm:ss.SSSFormat de-serialisiert werden LocalDateTime. Stellen Sie sicher, dass Datumszeichenfolge Datum und Uhrzeit mit Tnicht mit trennt space. Sekunden ( ss) und Millisekunden ( SSS) können weggelassen werden.

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

Wenn Sie Fastjson verwenden möchten, können Sie Ihr Problem lösen. Beachten Sie die Version

 <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.56</version>
 </dependency>
Byte Mamba
quelle
0

Wenn Sie dieses Problem aufgrund von GraphQL Java Tools haben und versuchen, ein Java Instantaus einer Datumszeichenfolge zu marshallen, müssen Sie Ihren SchemaParser so einrichten, dass ein ObjectMapper mit bestimmten Konfigurationen verwendet wird:

Fügen Sie in Ihrer GraphQLSchemaBuilder-Klasse ObjectMapper ein und fügen Sie die folgenden Module hinzu:

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

und fügen Sie es den Optionen hinzu:

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

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

Janac Meena
quelle
0

Wenn Sie Jackson Serializer verwenden , können Sie die Datumsmodule folgendermaßen verwenden:

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
quelle