sérialiser / désérialiser java 8 java.time avec le mappeur Jackson JSON


221

Comment utiliser le mappeur Jackson JSON avec Java 8 LocalDateTime?

org.codehaus.jackson.map.JsonMappingException: impossible d'instancier la valeur de type [type simple, classe java.time.LocalDateTime] à partir de la chaîne JSON; pas de constructeur / méthode d'usine à chaîne unique (via la chaîne de référence: MyDTO ["field1"] -> SubDTO ["date"])


4
êtes-vous sûr de vouloir mapper un LocalDateTime à JSon? Pour autant que je sache, JSon n'a pas de format pour les dates, bien que JavaScript utilise ISO-8601. Le problème est que LocalDateTime n'a pas de fuseau horaire. fuseau horaire). Si c'est ce que vous voulez faire, c'est bien sûr. Mais vérifiez simplement si vous avez envisagé d'utiliser ZonedDateTime à la place
arcuri82

Réponses:


276

Il n'est pas nécessaire d'utiliser ici des sérialiseurs / désérialiseurs personnalisés. Utilisez le module datetime de jackson-modules-java8 :

Module de type de données permettant à Jackson de reconnaître les types de données de l'API date et heure Java 8 (JSR-310).

Ce module ajoute le support de plusieurs classes:

  • Durée
  • Instant
  • LocalDateTime
  • LocalDate
  • Heure locale
  • MonthDay
  • OffsetDateTime
  • OffsetTime
  • Période
  • An
  • Année mois
  • ZonedDateTime
  • ZoneId
  • ZoneOffset

59
Oh oui! Je pensais que cela ne fonctionnait pas parce que je ne savais pas qu'il fallait faire une de ces personnalisations à l'objet mappeur: registerModule(new JSR310Module())ou findAndRegisterModules(). Voir github.com/FasterXML/jackson-datatype-jsr310 et voici comment personnaliser votre objet mappeur si vous utilisez le framework Spring: stackoverflow.com/questions/7854030/…
Alexander Taylor

10
Ne fonctionne pas avec le plus simple avec lequel j'ai essayé,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
Si vous utilisez le framework Spring récent, il n'est plus nécessaire de vous personnaliser, car les modules Jackson connus comme celui-ci sont désormais automatiquement enregistrés s'ils sont détectés sur le chemin de classe
vorburger

37
JSR310Module est déjà un peu dépassé, utilisez plutôt JavaTimeModule
Jacob Eckel

17
@MattBall Je suggère d' ajouter que, en plus d'utiliser jackson-datatype-jsr310, vous devez vous inscrire le JavaTimeModule avec votre objet mappeur: objectMapper.registerModule(new JavaTimeModule());. Tant sur la sérialisation que sur la désérialisation.
GrandAdmiral

76

Mise à jour: laisser cette réponse pour des raisons historiques, mais je ne la recommande pas. Veuillez voir la réponse acceptée ci-dessus.

Dites à Jackson de mapper à l'aide de vos classes de dé-sérialisation personnalisées:

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

fournir des classes personnalisées:

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

fait aléatoire: si je niche au-dessus des classes et ne les rend pas statiques, le message d'erreur est bizarre: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported


au moment de la rédaction de ce document, le seul avantage ici par rapport aux autres réponses ne dépend pas de FasterXML, quel qu'il soit.
Alexander Taylor


4
Oh je vois. tant d'anciennes entrées de pom dans le projet sur lesquelles je travaille. donc plus org.codehaus, c'est tout com.fasterxml maintenant. Merci!
Alexander Taylor

2
De plus, je n'ai pas pu utiliser celui intégré car j'avais besoin de passer le formateur ISO_DATE_TIME. Je ne sais pas s'il est possible de le faire lors de l'utilisation du JSR310Module.
isaac.hazan

En utilisant cela avec Spring, j'ai dû créer un bean des deux LocalDateTimeSerializeretLocalDateTimeDeserializer
BenR

53

Si vous utilisez la classe ObjectMapper de fastxml, par défaut ObjectMapper ne comprend pas la classe LocalDateTime, vous devez donc ajouter une autre dépendance dans votre gradle / maven:

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

Vous devez maintenant enregistrer le support de type de données offert par cette bibliothèque dans votre objet objectmapper, cela peut être fait en suivant:

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

Maintenant, dans votre jsonString, vous pouvez facilement mettre votre champ java.LocalDateTime comme suit:

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

En faisant tout cela, votre conversion de fichier Json en objet Java fonctionnera correctement, vous pouvez lire le fichier en suivant:

objectMapper.readValue(jsonString, new TypeReference<List<User>>() {
            });

4
findAndRegisterModules()était une pièce critique qui me manquait lors de la construction d'unObjectMapper
Xaero Degreaz

2
Et Instant?
powder366

1
J'ai également ajouté cette ligne: objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
Janet

voici le code complet nécessaire: ObjectMapper objectMapper = new ObjectMapper (); objectMapper.findAndRegisterModules (); objectMapper.configure (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
khush

42

Cette dépendance maven résoudra votre problème:

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

Une chose que j'ai eu du mal à faire est que le fuseau horaire de ZonedDateTime soit modifié en GMT pendant la désérialisation. Il s'est avéré que par défaut jackson le remplace par un autre du contexte. Pour conserver la zone, il faut désactiver cette «fonctionnalité»

Jackson2ObjectMapperBuilder.json()
    .featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)

5
Merci beaucoup pour l'indication DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE.
Andrei Urvantcev

5
En plus de désactiver, DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONEj'ai également dû désactiver SerializationFeature.WRITE_DATES_AS_TIMESTAMPSpour que tout commence à fonctionner comme il se doit.
Matt Watson

16

J'ai eu un problème similaire lors de l'utilisation de Spring Boot . Avec Spring boot 1.5.1.RELEASE, tout ce que j'avais à faire était d'ajouter une dépendance:

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

7

Si vous utilisez Jersey, vous devez ajouter la dépendance Maven (jackson-datatype-jsr310) comme les autres l'ont suggéré et enregistrer votre instance de mappeur d'objet comme suit:

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

Lorsque vous enregistrez Jackson dans vos ressources, vous devez ajouter ce mappeur comme suit:

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

6

Si vous ne pouvez pas utiliser jackson-modules-java8pour quelque raison que ce soit, vous pouvez (dé-) sérialiser le champ instantané en longutilisant @JsonIgnoreet @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);
    }
}

Exemple:

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

les rendements

{"epochTime":1506432517242}
2017-09-26T13:28:37.242Z

C'est une méthode très propre et permet aux utilisateurs de votre classe d'utiliser comme ils le souhaitent.
Ashhar Hasan, le

3

J'utilise ce format d'heure: "{birthDate": "2018-05-24T13:56:13Z}"pour désérialiser de json en java.time.Instant (voir capture d'écran)

entrez la description de l'image ici


3

Ceci est juste un exemple comment l'utiliser dans un test unitaire que j'ai piraté pour déboguer ce problème. Les ingrédients clés sont

  • mapper.registerModule(new JavaTimeModule());
  • dépendance maven de <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());
    }

}

2

Vous pouvez définir cela dans votre application.ymlfichier pour résoudre l'heure instantanée, qui est l'API Date dans java8:

spring.jackson.serialization.write-dates-as-timestamps=false

2

Si vous utilisez Spring Boot et que vous rencontrez ce problème avec OffsetDateTime, vous devez utiliser les registerModules comme indiqué ci-dessus par @greperror (répondu le 28 mai 16 à 13:04), mais notez qu'il existe une différence. La dépendance mentionnée n'a pas besoin d'être ajoutée car je suppose que Spring Boot l'a déjà. J'avais ce problème avec Spring Boot et cela a fonctionné pour moi sans ajouter cette dépendance.


1

Pour ceux qui utilisent Spring Boot 2.x

Il n'est pas nécessaire de faire ce qui précède - Java 8 LocalDateTime est sérialisé / désérialisé hors de la boîte. J'ai dû faire tout ce qui précède en 1.x, mais avec Boot 2.x, cela fonctionne de manière transparente.

Voir également cette référence Format JSON Java 8 LocalDateTime dans Spring Boot


1

Si quelqu'un a un problème lors de l'utilisation, SpringBootvoici comment j'ai résolu le problème sans ajouter de nouvelle dépendance.

In Spring 2.1.3Jackson s'attend à ce que la chaîne de date 2019-05-21T07:37:11.000dans ce yyyy-MM-dd HH:mm:ss.SSSformat se désérialise LocalDateTime. Assurez-vous que la chaîne de date sépare la date et l'heure avec Tpas avec space. secondes ( ss) et millisecondes ( SSS) peuvent être omises.

@JsonProperty("last_charge_date")
public LocalDateTime lastChargeDate;

1

Si vous utilisez Jackson Serializer , voici une façon d'utiliser les modules de date:

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

0

Si vous envisagez d'utiliser fastjson, vous pouvez résoudre votre problème, notez la version

 <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.56</version>
 </dependency>

0

Si vous rencontrez ce problème à cause des outils Java GraphQL et que vous essayez de marshaler un Java à Instantpartir d'une chaîne de date, vous devez configurer votre SchemaParser pour utiliser un ObjectMapper avec certaines configurations:

Dans votre classe GraphQLSchemaBuilder, injectez ObjectMapper et ajoutez ces modules:

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

et l'ajouter aux options:

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

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.