Format Java 8 LocalDate Jackson

139

Pour java.util.Date quand je le fais

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")  
  private Date dateOfBirth;

puis en requête JSON lorsque j'envoie

{ {"dateOfBirth":"01/01/2000"} }  

Ça marche.

Comment dois-je faire cela pour le champ LocalDate de Java 8 ?

J'ai essayé d'avoir

@JsonDeserialize(using = LocalDateDeserializer.class)  
@JsonSerialize(using = LocalDateSerializer.class)  
private LocalDate dateOfBirth;  

Ça n'a pas marché.

Quelqu'un peut-il s'il vous plaît me dire quelle est la bonne façon de procéder.

Voici les dépendances

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>jaxrs-api</artifactId>
     <version>3.0.9.Final</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.jaxrs</groupId>
    <artifactId>jackson-jaxrs-json-provider</artifactId>
    <version>2.4.2</version>
</dependency>
<dependency>
    <groupId>com.wordnik</groupId>
    <artifactId>swagger-annotations</artifactId>
    <version>1.3.10</version>
</dependency>
COUP
la source

Réponses:

106

Je n'ai jamais pu faire fonctionner cela simplement en utilisant des annotations. Pour le faire fonctionner, j'ai créé un ContextResolverfor ObjectMapper, puis j'ai ajouté le JSR310Module( update: now it is JavaTimeModuleinstead ), avec une autre mise en garde, qui était la nécessité de définir write-date-as-timestamp sur false. Pour en savoir plus, consultez la documentation du module JSR310 . Voici un exemple de ce que j'ai utilisé.

Dépendance

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

Remarque: un problème auquel j'ai été confronté est que la jackson-annotationversion extraite par une autre dépendance, utilisait la version 2.3.2, qui annulait la version 2.4 requise par le jsr310. Ce qui s'est passé, c'est que j'ai obtenu un NoClassDefFound pour ObjectIdResolver, qui est une classe 2.4. J'avais donc juste besoin d'aligner les versions de dépendances incluses

ContexteResolver

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {  
    private final ObjectMapper MAPPER;

    public ObjectMapperContextResolver() {
        MAPPER = new ObjectMapper();
        // Now you should use JavaTimeModule instead
        MAPPER.registerModule(new JSR310Module());
        MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

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

Classe de ressources

@Path("person")
public class LocalDateResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getPerson() {
        Person person = new Person();
        person.birthDate = LocalDate.now();
        return Response.ok(person).build();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createPerson(Person person) {
        return Response.ok(
                DateTimeFormatter.ISO_DATE.format(person.birthDate)).build();
    }

    public static class Person {
        public LocalDate birthDate;
    }
}

Tester

curl -v http://localhost:8080/api/person
Résultat: {"birthDate":"2015-03-01"}

curl -v -POST -H "Content-Type:application/json" -d "{\"birthDate\":\"2015-03-01\"}" http://localhost:8080/api/person
Résultat: 2015-03-01


Voir aussi ici pour la solution JAXB.

METTRE À JOUR

Le JSR310Moduleest obsolète à partir de la version 2.7 de Jackson. Au lieu de cela, vous devez enregistrer le module JavaTimeModule. C'est toujours la même dépendance.

Paul Samsotha
la source
1
Bonjour Peeskillet, le champ birthDate, est généré sous la forme "birthDate": {"year": 0, "month": "Month", "dayOfMonth": 0, "dayOfWeek": "DayOfWeek", "era": {" value ": 0}," dayOfYear ": 0," leapYear ": false," monthValue ": 0," chronology ": {" id ":" "," calendarType ":" "}} comment puis-je faire juste comme "birthDate" ???
JAB
Vérifiez que le ContextResolver est appelé. Ajoutez une instruction d'impression dans la getContextméthode. Si cette méthode est appelée, je ne vois pas de raison pour que cela ne fonctionne pas. S'il n'est pas appelé, il se peut que ce soit quelque chose qui doit être corrigé avec la configuration de l'application. Pour cela, j'aurais besoin de voir plus que ce que vous avez fourni. Comme la version Resteasy, les dépendances, la configuration de l'application web.xml ou la sous-classe Application. En gros, assez pour reproduire le problème
Paul Samsotha
ContextResolver ne s'appelle pas Peeskillet. Je le réinscrit dans web.xml en tant que <context-param> <param-name> resteasy.resources </param-name> <param-value> com.bac.ObjectMapperContextResolver </param-value> </context-param> question mise à jour pour les dépendances que
j'utilise
Swagger semble être le problème. Je dirais de le désactiver, mais vu à partir de cette question, il y a un problème qui a été classé, avec un conflit entre l'ObjectMapper de Swagger et la tentative d'utiliser le vôtre. Vous pouvez essayer de désactiver le leur, et dans le ContextResolver, définir toutes les configurations ObjectMappercomme le fait swagger (vous pouvez voir un lien dans la question). Je ne sais pas car je ne travaille pas beaucoup avec swagger. Mais je pense que le swagger est le principal problème, pourquoi le résolveur de contexte n'est pas appelé.
Paul Samsotha
Après d' autres essais, l'annotation ne travail. Même si nous devons utiliser Swagger ObjectMapper, puis configurez déjà les horodateurs comme faux pour nous. Cela devrait donc fonctionner. Pour une meilleure aide, je vous suggère fortement de fournir un MCVE qui démontre le problème.
Paul Samsotha
95

@JsonSerialize et @JsonDeserialize ont bien fonctionné pour moi. Ils éliminent le besoin d'importer le module jsr310 supplémentaire:

@JsonDeserialize(using = LocalDateDeserializer.class)  
@JsonSerialize(using = LocalDateSerializer.class)  
private LocalDate dateOfBirth;

Désérialiseur:

public class LocalDateDeserializer extends StdDeserializer<LocalDate> {

    private static final long serialVersionUID = 1L;

    protected LocalDateDeserializer() {
        super(LocalDate.class);
    }


    @Override
    public LocalDate deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        return LocalDate.parse(jp.readValueAs(String.class));
    }

}

Sérialiseur:

public class LocalDateSerializer extends StdSerializer<LocalDate> {

    private static final long serialVersionUID = 1L;

    public LocalDateSerializer(){
        super(LocalDate.class);
    }

    @Override
    public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider sp) throws IOException, JsonProcessingException {
        gen.writeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
    }
}
Christopher Yang
la source
2
C'est la meilleure réponse pour moi. Merci!
Romeo Jr Maranan
7
Ces classes sont incluses dans jackson-datatype-jsr310. Pas besoin de les définir manuellement dans votre projet.
NeuroXc
1
Cette solution a fonctionné pour moi, en utilisant les sérialiseurs dans jackson-datatype-jsr310.
dave
2
Cela devrait être la nouvelle meilleure réponse.
Doctor Parameter
1
Si vous utilisez des sérialiseurs et des désérialiseurs dans jackson-datatype-jsr310, mieux vaut ajouter @JsonFormat (shape = JsonFormat.Shape.STRING) à votre champ. Sans ce format, la valeur sera sérialisée comme [année, mois, jour], bien que la désérialisation fonctionnera.
Jian Chen
74
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

fonctionne bien pour moi.

欧阳 世雄
la source
2
new com.fasterxml.jackson.datatype.jsr310.JSR310Module()pour la version 2.5.4 de Jackson. La classe JavaTimeModule n'existe pas dans cette version.
user3774109
Cette réponse fonctionne également pour LocalDateTime(jackson 2.9.5). 1 dépendance supplémentaire requise, donc mon build.sbt ressemble à:"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.5", "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.5"
ruhong
2
Cela devrait avoir beaucoup plus de votes. Simple et efficace.
mkasberg
A parfaitement fonctionné! Je vous remercie.
MAD-HAX
Cela m'a orienté dans la bonne direction, merci! J'ajouterais que dans spring-boot, tout ce que vous avez à faire est d'ajouter ce qui suit à application.properties: spring.jackson.serialization.write-dates-as-timestamps = false
Joost Lambregts
46

Dans l'application Web Spring Boot, avec Jackson et JSR 310 version "2.8.5"

compile "com.fasterxml.jackson.core:jackson-databind:2.8.5"
runtime "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.5"

Les @JsonFormatoeuvres:

import com.fasterxml.jackson.annotation.JsonFormat;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate birthDate;
Tsolak Barseghyan
la source
2
Cela fonctionne-t-il pour la désérialisation? ou seulement la sérialisation? Pas de succès avec la désérialisation
rewolf
7
J'ai dû déclarer explicitement le désérialiseur@JsonDeserialize(using= LocalDateDeserializer.class)
rewolf
1
de loin le plus simple!
Antonio
@JsonFormatjuste pour changer le format des données de sortie. stackoverflow.com/a/53251526/816759 fonctionne parfaitement avec @JsonFormat, @JsonDeserialize,@JsonSerialize
Baha
Dans Spring Boot, une fois que vous avez ajouté la dépendance JSR310, tout ce que vous avez à faire est de l'ajouter spring.jackson.serialization.write-dates-as-timestamps=falseà votre application.properties, et il le formate yyyy-MM-ddautomatiquement. Pas besoin de@JsonFormat
esfandia
31

La solution la plus simple (qui prend également en charge la désérialisation et la sérialisation) est

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
private LocalDate dateOfBirth;

Lors de l'utilisation des dépendances suivantes dans votre projet.

Maven

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.9.7</version>
</dependency>
<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-jsr310</artifactId>
   <version>2.9.7</version>
</dependency>

Gradle

compile "com.fasterxml.jackson.core:jackson-databind:2.9.7"
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.7"

Aucune implémentation supplémentaire d'un ContextResolver, Serializer ou Deserializer n'est requise.

Paul Wasilewski
la source
Brillant, de loin le plus simple. Pour info, pour toute personne ayant de nombreuses dépendances, j'ai dû mettre à jour d'autres bibliothèques qui incorporaient des annotations jackson.
brt
Cette réponse est la plus proche que j'ai pour résoudre mon problème. La sérialisation fonctionne, mais la désérialisation échoue à cause du modèle que j'ai utilisé avec @JsonFormat je pense (@JsonFormat (shape = JsonFormat.Shape.STRING, pattern = "jj-MM-aaaa_HH: mm: SS").
fsakiyama
Si votre désérialisation a échoué, il est fort probable que vous ObjectMapperne vous soyez pas JavaTimeModuleinscrit. Si votre instance ObjectMapper est fournie à partir du framework spring / MessageConverter. Ils ont fait de la magie pour les relier. Dans les autres cas, devrait registerModuleactiver LocalDateDeserializerpar défaut pour tous les "LocalDate" dans POJO
Dennis C
19

Depuis le LocalDateSerializertransforme en "[année, mois, jour]" (un tableau json) plutôt qu'en "année-mois-jour" (une chaîne json) par défaut, et puisque je ne veux pas avoir besoin de ObjectMapperconfiguration spéciale (vous pouvez faites LocalDateSerializergénérer des chaînes si vous désactivez SerializationFeature.WRITE_DATES_AS_TIMESTAMPSmais cela nécessite une configuration supplémentaire à votreObjectMapper ), j'utilise ce qui suit:

importations:

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;

code:

// generates "yyyy-MM-dd" output
@JsonSerialize(using = ToStringSerializer.class)
// handles "yyyy-MM-dd" input just fine (note: "yyyy-M-d" format will not work)
@JsonDeserialize(using = LocalDateDeserializer.class)
private LocalDate localDate;

Et maintenant, je peux simplement utiliser new ObjectMapper()pour lire et écrire mes objets sans aucune configuration particulière.

Homme de l'ombre
la source
3
Une chose que j'aimerais ajouter est de transmettre la date car "2018-12-07"au lieu de "2018-12-7"cela, vous obtiendrez une erreur.
Kid101
1
Correct, il fonctionne avec le yyyy-MM-ddformat (mois et jour à 2 chiffres) et non avec le format yyyy-M-d(mois ou jour à 1 chiffre).
Shadow Man
8

Juste une mise à jour de la réponse de Christopher.

Depuis la version 2.6.0

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

Utilisez JavaTimeModule au lieu de JSR310Module (obsolète).

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {  
    private final ObjectMapper MAPPER;

    public ObjectMapperContextResolver() {
        MAPPER = new ObjectMapper();
        MAPPER.registerModule(new JavaTimeModule());
        MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

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

Selon la documentation , le nouveau JavaTimeModule utilise les mêmes paramètres standard pour la sérialisation par défaut qui n'utilise PAS les ID de fuseau horaire, et utilise à la place uniquement les décalages de fuseau horaire conformes à ISO-8601.

Le comportement peut être modifié à l'aide de SerializationFeature.WRITE_DATES_WITH_ZONE_ID

bdzzaid
la source
Cela m'a aidé. Dans mon cas, j'avais besoin d'ajouter la MAPPER.registerModule(new JavaTimeModule());ligne. Cela m'a permis de formater les objets LocalDate au format "2020-02-20". Je n'avais pas besoin de la MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);ligne, pour ce que je cherchais
Cuga
6

L'annotation suivante a bien fonctionné pour moi.

Aucune dépendance supplémentaire nécessaire.

    @JsonProperty("created_at")
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime createdAt;
KayV
la source
5
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createdDate;
slisnychyi
la source
4

https://stackoverflow.com/a/53251526/1282532 est le moyen le plus simple de sérialiser / désérialiser la propriété. J'ai deux préoccupations concernant cette approche - jusqu'à un certain point la violation du principe DRY et un couplage élevé entre pojo et mapper.

public class Trade {
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate tradeDate;
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate maturityDate;
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate entryDate;
}

Dans le cas où vous avez POJO avec plusieurs champs LocalDate, il est préférable de configurer mapper au lieu de POJO. Cela peut être aussi simple que https://stackoverflow.com/a/35062824/1282532 si vous utilisez des valeurs ISO-8601 ("2019-01-31")

Au cas où vous auriez besoin de gérer un format personnalisé, le code ressemblera à ceci:

ObjectMapper mapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyyMMdd")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyyMMdd")));
mapper.registerModule(javaTimeModule);

La logique est écrite une seule fois, elle peut être réutilisée pour plusieurs POJO

Pavel
la source
3

Le plus simple et le plus court à ce jour:

@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate localDate;

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;

aucune dépendance requise avec Spring boot> = 2.2+

Anil Bhaskar
la source
1

À partir de 2020 et Jackson 2.10.1, aucun code spécial n'est nécessaire, il suffit de dire à Jackson ce que vous voulez:

ObjectMapper objectMapper = new ObjectMapper();

// Register module that knows how to serialize java.time objects
// Provided by jackson-datatype-jsr310
objectMapper.registerModule(new JavaTimeModule());

// Ask Jackson to serialize dates as String (ISO-8601 by default)
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

Cela a déjà été mentionné dans cette réponse , j'ajoute un test unitaire vérifiant la fonctionnalité:

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.Data;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class LocalDateSerializationTest {

    @Data
    static class TestBean {
        // Accept default ISO-8601 format
        LocalDate birthDate;
        // Use custom format
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")
        LocalDate birthDateWithCustomFormat;
    }

    @Test
    void serializeDeserializeTest() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();

        // Register module that knows how to serialize java.time objects
        objectMapper.registerModule(new JavaTimeModule());

        // Ask Jackson to serialize dates as String (ISO-8601 by default)
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        // The JSON string after serialization
        String json = "{\"birthDate\":\"2000-01-02\",\"birthDateWithCustomFormat\":\"03/02/2001\"}";

        // The object after deserialization
        TestBean object = new TestBean();
        object.setBirthDate(LocalDate.of(2000, 1, 2));
        object.setBirthDateWithCustomFormat(LocalDate.of(2001, 2, 3));

        // Assert serialization
        assertEquals(json, objectMapper.writeValueAsString(object));

        // Assert deserialization
        assertEquals(object, objectMapper.readValue(json, TestBean.class));
    }
}

TestBean utilise Lombok pour générer le passe-partout pour le bean.

Bogdan Calmac
la source
0

Dans la classe de configuration, définissez la classe LocalDateSerializer et LocalDateDeserializer et enregistrez-les dans ObjectMapper via JavaTimeModule comme ci-dessous:

@Configuration
public class AppConfig
{
@Bean
    public ObjectMapper objectMapper()
    {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(Include.NON_EMPTY);
        //other mapper configs
        // Customize de-serialization


        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer());
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer());
        mapper.registerModule(javaTimeModule);

        return mapper;
    }

    public class LocalDateSerializer extends JsonSerializer<LocalDate> {
        @Override
        public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeString(value.format(Constant.DATE_TIME_FORMATTER));
        }
    }

    public class LocalDateDeserializer extends JsonDeserializer<LocalDate> {

        @Override
        public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            return LocalDate.parse(p.getValueAsString(), Constant.DATE_TIME_FORMATTER);
        }
    }
}
nanosoft
la source
0

Si votre demande contient un objet comme celui-ci:

{
    "year": 1900,
    "month": 1,
    "day": 20
}

Ensuite, vous pouvez utiliser:

data class DateObject(
    val day: Int,
    val month: Int,
    val year: Int
)
class LocalDateConverter : StdConverter<DateObject, LocalDate>() {
    override fun convert(value: DateObject): LocalDate {
        return value.run { LocalDate.of(year, month, day) }
    }
}

Au-dessus du terrain:

@JsonDeserialize(converter = LocalDateConverter::class)
val dateOfBirth: LocalDate

Le code est en Kotlin mais cela fonctionnerait aussi bien sûr pour Java.

mowwwalker
la source