serializować / deserializować java 8 java.time z maperem Jackson JSON

221

Jak korzystać z programu mapującego Jackson JSON z Java 8 LocalDateTime?

org.codehaus.jackson.map.JsonMappingException: Nie można utworzyć wystąpienia wartości typu [typ prosty, klasa java.time.LocalDateTime] z ciągu JSON; brak metody konstruktora / fabryki o jednym łańcuchu (poprzez łańcuch referencyjny: MyDTO [„field1”] -> SubDTO [„data”])

Alexander Taylor
źródło
4
czy na pewno chcesz zmapować LocalDateTime na JSon? O ile mi wiadomo, JSon nie ma formatu dat, chociaż JavaScript używa ISO-8601. Problem w tym, że LocalDateTime nie ma strefy czasowej ... więc jeśli użyjesz JSON jako medium do wysyłania informacji o dacie / czasie, możesz mieć kłopoty, jeśli klient zinterpretuje brak strefy czasowej jako domyślny UTC (lub własny strefa czasowa). Jeśli to właśnie chcesz zrobić, oczywiście jest w porządku. Ale po prostu sprawdź, czy zamiast tego rozważałeś użycie ZonedDateTime
arcuri82,

Odpowiedzi:

276

Nie ma potrzeby używania niestandardowych serializatorów / deserializatorów. Użyj modułu datetime dla jackson-modules-java8 :

Moduł typu danych, dzięki któremu Jackson rozpoznaje typy danych Java 8 Date & Time API (JSR-310).

Ten moduł dodaje obsługę kilku klas:

  • Trwanie
  • Natychmiastowy
  • LocalDateTime
  • Data lokalna
  • Czas lokalny
  • Dzień miesiąca
  • OffsetDateTime
  • OffsetTime
  • Kropka
  • Rok
  • YearMonth
  • ZonedDateTime
  • ZoneId
  • ZoneOffset
Matt Ball
źródło
59
O tak! Myślałem, że to nie działa, ponieważ nie zdawałem sobie sprawy, że trzeba wykonać jedną z tych modyfikacji obiektu mapującego: registerModule(new JSR310Module())lub findAndRegisterModules(). Zobacz github.com/FasterXML/jackson-datatype-jsr310, a oto, jak dostosować obiekt mapujący, jeśli używasz frameworka Spring: stackoverflow.com/questions/7854030/...
Alexander Taylor
10
Nie działa z najprostszym, z którym próbowałem,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
Jeśli korzystasz z najnowszego frameworka Spring, nie musisz już dostosowywać się, a znane moduły Jacksona, takie jak ten, są teraz automatycznie rejestrowane, jeśli zostaną wykryte w ścieżce klas: spring.io/blog/2014/12/02/…
vorburger
36
JSR310Module jest już trochę przestarzały, zamiast tego użyj JavaTimeModule
Jacob Eckel
17
@MattBall Proponuję dodać, że oprócz korzystania Jackson-datatype-jsr310, trzeba zarejestrować JavaTimeModule z obiektem odwzorowującym: objectMapper.registerModule(new JavaTimeModule());. Zarówno w przypadku serializacji, jak i deserializacji.
GrandAdmiral
76

Aktualizacja: Pozostawienie tej odpowiedzi ze względów historycznych, ale nie polecam jej. Zobacz zaakceptowaną odpowiedź powyżej.

Powiedz Jacksonowi, aby zmapował za pomocą niestandardowych klas serializacji [de]:

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

zapewnić niestandardowe klasy:

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

fakt losowy: jeśli zagnieżdżę się nad klasami i nie ustawię ich w sposób statyczny, komunikat o błędzie jest dziwny: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported

Alexander Taylor
źródło
od tego momentu jedyną przewagą nad innymi odpowiedziami nie jest zależność od FasterXML, cokolwiek to jest.
Alexander Taylor
4
FasterXML to Jackson. szybciejxml.com github.com/fasterxml/jackson
Matt Ball
4
rozumiem. tyle starożytnych wpisów pom w projekcie, nad którym pracuję. więc nie ma już org.codehaus, teraz jest już tylko com.fasterxml. dzięki!
Alexander Taylor
2
Nie mogłem też użyć wbudowanego, ponieważ musiałem przekazać formatyzator ISO_DATE_TIME. Nie jestem pewien, czy jest to możliwe w przypadku korzystania z JSR310Module.
isaac.hazan
Korzystając z tego ze LocalDateTimeSerializerLocalDateTimeDeserializer
Springem,
53

Jeśli używasz klasy ObjectMapper szybciej-ml, domyślnie ObjectMapper nie rozumie klasy LocalDateTime, więc musisz dodać inną zależność w swoim gradle / maven:

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

Teraz musisz zarejestrować obsługę typów danych oferowaną przez tę bibliotekę w obiekcie objectmapper. Można to zrobić, wykonując następujące czynności:

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

Teraz w jsonString możesz łatwo umieścić pole java.LocalDateTime w następujący sposób:

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

Robiąc to wszystko, konwersja pliku Json na obiekt Java będzie działać poprawnie, możesz odczytać plik, wykonując następujące czynności:

objectMapper.readValue(jsonString, new TypeReference<List<User>>() {
            });
greperror
źródło
4
findAndRegisterModules()był krytyczny kawałek że brakowało mi podczas konstruowaniaObjectMapper
Xaero Degreaz
2
Co z Instant?
powder366
1
Dodałem również ten wiersz: objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Janet
jest to pełny potrzebny kod: ObjectMapper objectMapper = new ObjectMapper (); objectMapper.findAndRegisterModules (); objectMapper.configure (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
khush
42

Ta zależność od raju rozwiąże twój problem:

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

Jedną z rzeczy, z którą się zmagałem, jest zmiana strefy czasowej ZonedDateTime na GMT podczas deserializacji. Okazało się, że domyślnie Jackson zastępuje go jednym z kontekstu. Aby zachować strefę, należy wyłączyć tę „funkcję”

Jackson2ObjectMapperBuilder.json()
    .featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
biorę
źródło
5
Wielkie dzięki za podpowiedź DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE.
Andrei Urvantcev
5
Oprócz wyłączenia DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONEmusiałem również wyłączyć, SerializationFeature.WRITE_DATES_AS_TIMESTAMPSaby wszystko zaczęło działać tak, jak powinno.
Matt Watson,
16

Miałem podobny problem podczas używania rozruchu wiosennego . W przypadku rozruchu wiosennego 1.5.1. ZWOLNIENIEM wystarczyło dodać zależność:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Witold Kaczurba
źródło
7

Jeśli używasz Jersey, musisz dodać zależność Maven (jackson-datatype-jsr310), jak sugerowali inni, i zarejestrować instancję mapowania obiektów w następujący sposób:

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

Podczas rejestracji Jacksona w swoich zasobach musisz dodać tego mapera w następujący sposób:

final ResourceConfig rc = new ResourceConfig().packages("<your package>");
rc
  .register(JacksonObjectMapper.class)
  .register(JacksonJaxbJsonProvider.class);
Sul Aga
źródło
6

Jeśli nie możesz użyć jackson-modules-java8z jakiegokolwiek powodu, możesz (od-) serializować natychmiastowe pole jako longużycie @JsonIgnorei @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);
    }
}

Przykład:

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

daje

{"epochTime":1506432517242}
2017-09-26T13:28:37.242Z
Udo
źródło
Jest to bardzo czysta metoda i pozwala użytkownikom twojej klasy korzystać z nich w dowolny sposób.
Ashhar Hasan
3

Używam tego formatu czasu: "{birthDate": "2018-05-24T13:56:13Z}"do deserializacji z Jsona do java.time.Instant (patrz zrzut ekranu)

wprowadź opis zdjęcia tutaj

Taras Melnyk
źródło
3

To tylko przykład, jak użyć go w teście jednostkowym, który zhackowałem w celu debugowania tego problemu. Kluczowymi składnikami są

  • mapper.registerModule(new JavaTimeModule());
  • zależność od maven <artifactId>jackson-datatype-jsr310</artifactId>

Kod:

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
źródło
2

Jeśli używasz Spring boot i masz ten problem z OffsetDateTime, musisz użyć registerModules, jak odpowiedział powyżej @greperror (odpowiedział 28 maja o 13:04), ale zauważ, że jest jedna różnica. Wspomnianej zależności nie trzeba dodawać, ponieważ domyślam się, że wiosenny rozruch już ją ma. Miałem ten problem z wiosennym bootowaniem i działało to dla mnie bez dodawania tej zależności.

VC2019
źródło
1

Możesz ustawić to w swoim application.ymlpliku, aby rozpoznać natychmiastowy czas, czyli Date API w java8:

spring.jackson.serialization.write-dates-as-timestamps=false
Janki
źródło
1

Dla tych, którzy używają Spring Boot 2.x

Nie trzeba wykonywać żadnej z powyższych czynności - Java 8 LocalDateTime jest serializowany / deserializowany po wyjęciu z pudełka. Musiałem zrobić wszystkie powyższe w 1.x, ale z Boot 2.x działa bezproblemowo.

Zobacz także to odniesienie Format JSON Java 8 LocalDateTime w Spring Boot

HopeKing
źródło
1

Jeśli ktoś ma problem podczas korzystania z tego SpringBoottutaj, to w jaki sposób naprawiłem problem bez dodawania nowej zależności.

W Spring 2.1.3Jackson oczekuje, że ciąg daty 2019-05-21T07:37:11.000w tym yyyy-MM-dd HH:mm:ss.SSSformacie usunie się z serializacji LocalDateTime. Upewnij się, że ciąg daty oddziela datę i godzinę, a Tnie znakiem space. sekundy ( ss) i milisekundy ( SSS) mogą zostać pominięte.

@JsonProperty("last_charge_date")
public LocalDateTime lastChargeDate;
Muhammad Usman
źródło
0

Jeśli rozważasz użycie fastjson, możesz rozwiązać swój problem, zanotuj wersję

 <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.56</version>
 </dependency>
bajt mamba
źródło
0

Jeśli masz ten problem z powodu GraphQL Java Tools i próbujesz uporządkować Javę Instantz ciągu daty, musisz skonfigurować SchemaParser, aby używał ObjectMapper w określonych konfiguracjach:

W klasie GraphQLSchemaBuilder wstrzyknij ObjectMapper i dodaj te moduły:

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

i dodaj go do opcji:

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

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

Janac Meena
źródło
0

Jeśli korzystasz z Jackson Serializer , oto sposób na użycie modułów daty:

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
źródło