web-dev-qa-db-fra.com

Correspondance du format de date à JSON Jackson

J'ai un format de date venant de l'API comme ceci:

"start_time": "2015-10-1 3:00 PM GMT+1:00"

Quel est l'horodatage AAAA-JJ-MM HH: MM am/pm GMT. Je mappe cette valeur à une variable Date dans POJO. De toute évidence, son erreur de conversion montrant.

J'aimerais savoir 2 choses:

  1. Quel format dois-je utiliser pour effectuer la conversion avec Jackson? Date est-il un bon type de champ pour cela?
  2. En général, existe-t-il un moyen de traiter les variables avant qu'elles ne soient mappées aux membres Object par Jackson? Quelque chose comme, changer le format, les calculs, etc.
124
Kevin Rave

Quel format dois-je utiliser pour effectuer la conversion avec Jackson? Date est-il un bon type de champ pour cela?

Date est un type de champ fin pour cela. Vous pouvez facilement analyser le JSON en utilisant ObjectMapper.setDateFormat:

DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
myObjectMapper.setDateFormat(df);

En général, existe-t-il un moyen de traiter les variables avant qu'elles ne soient mappées aux membres Object par Jackson? Quelque chose comme, changer le format, les calculs, etc.

Oui. Vous disposez de quelques options, notamment la mise en œuvre d’une stratégie personnalisée JsonDeserializer, par exemple. étendre JsonDeserializer<Date>. Ceci est un bon début.

104
pb2q

Depuis Jackson v2.0, vous pouvez utiliser l'annotation @JsonFormat directement sur les membres Object.

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;
288
Olivier Lecrivain

Bien sûr il existe un moyen automatisé appelé sérialisation et désérialisation et vous pouvez le définir avec des annotations spécifiques ( @ JsonSerialize , @ JsonDeserialize ) comme mentionné par pb2q également.

Vous pouvez utiliser à la fois Java.util.Date et Java.util.Calendar ... et probablement JodaTime.

Les annotations @JsonFormat ne fonctionnaient pas pour moi comme je le voulais (il a ajusté le fuseau horaire à une valeur différente) pendant la désérialisation (la sérialisation a fonctionné parfaitement):

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "CET")

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Europe/Budapest")

Vous devez utiliser un sérialiseur personnalisé et un désérialiseur personnalisé à la place de l'annotation @JsonFormat si vous voulez un résultat prédit. J'ai trouvé un bon tutoriel et une solution ici - http://www.baeldung.com/jackson-serialize-dates

Il y a des exemples pour les champs Date mais il me fallait pour les champs Calendrier donc voici ma mise en oeuvre :

Le sérialiseur :

public class CustomCalendarSerializer extends JsonSerializer<Calendar> {

    public static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    public static final Locale LOCALE_HUNGARIAN = new Locale("hu", "HU");
    public static final TimeZone LOCAL_TIME_ZONE = TimeZone.getTimeZone("Europe/Budapest");

    @Override
    public void serialize(Calendar value, JsonGenerator gen, SerializerProvider arg2)
            throws IOException, JsonProcessingException {
        if (value == null) {
            gen.writeNull();
        } else {
            gen.writeString(FORMATTER.format(value.getTime()));
        }
    }
}

Le désérialiseur :

public class CustomCalendarDeserializer extends JsonDeserializer<Calendar> {

    @Override
    public Calendar deserialize(JsonParser jsonparser, DeserializationContext context)
            throws IOException, JsonProcessingException {
        String dateAsString = jsonparser.getText();
        try {
            Date date = CustomCalendarSerializer.FORMATTER.parse(dateAsString);
            Calendar calendar = Calendar.getInstance(
                CustomCalendarSerializer.LOCAL_TIME_ZONE, 
                CustomCalendarSerializer.LOCALE_HUNGARIAN
            );
            calendar.setTime(date);
            return calendar;
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

et l'utilisation des classes ci-dessus:

public class CalendarEntry {

    @JsonSerialize(using = CustomCalendarSerializer.class)
    @JsonDeserialize(using = CustomCalendarDeserializer.class)
    private Calendar calendar;

    // ... additional things ...
}

En utilisant cette implémentation, l'exécution du processus de sérialisation et de désérialisation aboutit consécutivement à la valeur Origin.

Uniquement en utilisant l'annotation @JsonFormat, la désérialisation donne un résultat différent Je pense qu'en raison de la configuration par défaut du fuseau horaire de la bibliothèque, vous ne pouvez pas modifier les paramètres d'annotation (c'était mon expérience avec la bibliothèque Jackson 2.5.3 et 2.6.3 ainsi).

45
Miklos Krivan

Juste un exemple complet pour l'application Spring Boot avec le format RFC3339 datetime

package bj.demo;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;

import Java.text.SimpleDateFormat;

/**
 * Created by [email protected] at 2018/5/4 10:22
 */
@SpringBootApplication
public class BarApp implements ApplicationListener<ApplicationReadyEvent> {

    public static void main(String[] args) {
        SpringApplication.run(BarApp.class, args);
    }

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
    }
}
3
BaiJiFeiLong

S'appuyant sur la réponse très utile de @ miklov-kriven, j'espère que ces deux points de considération supplémentaires seront utiles à quelqu'un:

(1) Je trouve une bonne idée d'inclure le sérialiseur et le désérialiseur en tant que classes internes statiques dans la même classe. NB: utilisation de ThreadLocal pour la sécurité des threads de SimpleDateFormat.

public class DateConverter {

    private static final ThreadLocal<SimpleDateFormat> sdf = 
        ThreadLocal.<SimpleDateFormat>withInitial(
                () -> {return new SimpleDateFormat("yyyy-MM-dd HH:mm a z");});

    public static class Serialize extends JsonSerializer<Date> {
        @Override
        public void serialize(Date value, JsonGenerator jgen SerializerProvider provider) throws Exception {
            if (value == null) {
                jgen.writeNull();
            }
            else {
                jgen.writeString(sdf.get().format(value));
            }
        }
    }

    public static class Deserialize extends JsonDeserializer<Date> {
        @Overrride
        public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws Exception {
            String dateAsString = jp.getText();
            try {
                if (Strings.isNullOrEmpty(dateAsString)) {
                    return null;
                }
                else {
                    return new Date(sdf.get().parse(dateAsString).getTime());
                }
            }
            catch (ParseException pe) {
                throw new RuntimeException(pe);
            }
        }
    }
}

(2) Au lieu d'utiliser les annotations @JsonSerialize et @JsonDeserialize sur chaque membre de classe, vous pouvez également remplacer la sérialisation par défaut de Jackson en appliquant la sérialisation personnalisée au niveau de l'application. Tous les membres de la classe de type Date seront sérialisés par Jackson. en utilisant cette sérialisation personnalisée sans annotation explicite sur chaque champ. Si vous utilisez Spring Boot, par exemple, vous pouvez procéder comme suit:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public Module customModule() {
        SimpleModule module = new SimpleModule();
        module.addSerializer(Date.class, new DateConverter.Serialize());
        module.addDeserializer(Date.class, new Dateconverter.Deserialize());
        return module;
    }
}
1
Stuart

Si quelqu'un a des problèmes d'utilisation d'un dateformat personnalisé pour Java.sql.Date, voici la solution la plus simple:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Java.sql.Date.class, new DateSerializer());
mapper.registerModule(module);

(Cette réponse SO m'a évité beaucoup d'ennuis: https://stackoverflow.com/a/35212795/3149048 )

Jackson utilise par défaut SqlDateSerializer pour Java.sql.Date, mais actuellement, ce sérialiseur ne prend pas le compte de dateformat en compte, consultez ce problème: https://github.com/FasterXML/jackson-databind/issues/1407 . La solution de contournement consiste à enregistrer un sérialiseur différent pour Java.sql.Date, comme indiqué dans l'exemple de code.

1
Stephanie

Travailler pour moi SpringBoot.

 import com.alibaba.fastjson.annotation.JSONField;

 @JSONField(format = "yyyy-MM-dd HH:mm:ss")  
 private Date createTime;

sortie:

{ 
   "createTime": "2019-06-14 13:07:21"
}
1
anson

Je tiens à souligner que la définition d'une SimpleDateFormat comme décrit dans l'autre réponse ne fonctionne que pour un Java.util.Date dont je suppose qu'elle est visée dans la question. Mais pour Java.sql.Date le formateur ne fonctionne pas. Dans mon cas, la raison pour laquelle le formateur ne fonctionnait pas était claire, car dans le modèle à modéliser en série, le champ était en fait un Java.utl.Date, mais l'objet lui-même avait fini par être un Java.sql.Date. C'est possible parce que

public class Java.sql extends Java.util.Date

Donc, c'est en fait valide

Java.util.Date date = new Java.sql.Date(1542381115815L);

Donc, si vous vous demandez pourquoi votre champ Date n'est pas correctement formaté, assurez-vous que l'objet est vraiment un Java.util.Date.

Ici est également mentionné pourquoi la gestion de Java.sql.Date ne sera pas ajoutée.

Ce serait alors briser le changement, et je ne pense pas que cela soit justifié. Si nous partions de zéro, je serais d'accord avec le changement, mais comme les choses ne sont pas tellement.

1
LazerBass