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"])

Alexander Taylor
la source
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
Matt Ball
la source
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

Alexander Taylor
la source
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
FasterXML est Jackson. plus rapidexml.com github.com/fasterxml/jackson
Matt Ball
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>>() {
            });
greperror
la source
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)
Je prends
la source
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>
Witold Kaczurba
la source
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);
Sul Aga
la source
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
Tu fais
la source
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

Taras Melnyk
la source
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());
    }

}
Mircea Stanciu
la source
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
Janki
la source
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.

VC2019
la source
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

HopeKing
la source
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;
Muhammad Usman
la source
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;
    }
}
edubriguenti
la source
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>
octet mamba
la source
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

Janac Meena
la source