web-dev-qa-db-fra.com

Désérialiser le format de date JSON en ZonedDateTime à l'aide de objectMapper

Contexte

  1. J'ai le JSON suivant (message de Kafka)
{
      "markdownPercentage": 20,
      "currency": "SEK",
      "startDate": "2019-07-25"
}
  1. J'ai le POJO suivant (généré par le schéma JSON) (je ne peux pas changer le POJO car il s'agit d'une ressource partagée dans l'entreprise)
public class Markdown {
    @JsonProperty("markdownPercentage")
    @NotNull
    private Integer markdownPercentage = 0;
    @JsonProperty("currency")
    @NotNull
    private String currency = "";
    @JsonFormat(
        shape = Shape.STRING,
        pattern = "yyyy-MM-dd"
    )
    @JsonProperty("startDate")
    @NotNull
    private ZonedDateTime startDate;

    // Constructors, Getters, Setters etc.

}
  1. Notre application est une application Spring Boot qui lit le message JSON (1) à partir de Kafka en utilisant Spring Cloud Stream et utilise le POJO (2) puis fait des trucs avec.

Problème

Lorsque l'application tente de désérialiser le message vers l'objet, elle lève l'exception suivante

com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `Java.time.ZonedDateTime` from String "2019-07-25": Failed to deserialize Java.time.ZonedDateTime: (Java.time.DateTimeException) Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2019-07-25 of type Java.time.format.Parsed
 at [Source: (String)"{"styleOption":"so2_GreyMelange_1563966403695_1361997740","markdowns":[{"markdownPercentage":20,"currency":"SEK","startDate":"2019-07-25"},{"markdownPercentage":20,"currency":"NOK","startDate":"2019-07-25"},{"markdownPercentage":20,"currency":"CHF","startDate":"2019-07-25"}]}"; line: 1, column: 126] (through reference chain: com.bestseller.generated.interfacecontracts.kafkamessages.pojos.markdownScheduled.MarkdownScheduled["markdowns"]->Java.util.ArrayList[0]->com.bestseller.generated.interfacecontracts.kafkamessages.pojos.markdownScheduled.Markdown["startDate"])

    at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.Java:67)
    at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.Java:1549)
    at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.Java:911)
    at com.fasterxml.jackson.datatype.jsr310.deser.JSR310DeserializerBase._handleDateTimeException(JSR310DeserializerBase.Java:80)
    at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.Java:212)
    at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.Java:50)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.Java:127)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.Java:288)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.Java:151)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.Java:286)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.Java:245)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.Java:27)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.Java:127)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.Java:288)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.Java:151)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.Java:4013)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.Java:3004)
    at com.bestseller.mps.functional.TestingConfiguration.test(TestingConfiguration.Java:42)
    at Java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at Java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:62)
    at Java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43)
    at Java.base/Java.lang.reflect.Method.invoke(Method.Java:566)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.Java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.Java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.Java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.Java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.Java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.Java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.Java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.Java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.Java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.Java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.Java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.Java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.Java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.Java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.Java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.Java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.Java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.Java:70)
Caused by: Java.time.DateTimeException: Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2019-07-25 of type Java.time.format.Parsed
    at Java.base/Java.time.ZonedDateTime.from(ZonedDateTime.Java:566)
    at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.Java:207)
    ... 35 more
Caused by: Java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor: {},ISO resolved to 2019-07-25 of type Java.time.format.Parsed
    at Java.base/Java.time.ZoneId.from(ZoneId.Java:463)
    at Java.base/Java.time.ZonedDateTime.from(ZonedDateTime.Java:554)
    ... 36 more

Code actuel

J'ai le objectMapper suivant défini

/**
     * Date mapper.
     *
     * @return the {@link ObjectMapper}
     */
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        return mapper;
    }

Question

Je comprends que le ZonedDateTime résultant dans le POJO a besoin d'un élément "time" qui n'est pas présent dans le message source. Je contrôle uniquement ObjectMapper. Y a-t-il une configuration possible qui puisse faire fonctionner cela?

Remarque

Je vais bien si l'élément de temps dans le POJO désérialisé est "supposé" être startOfDay, c'est-à-dire "00.00.00.000Z"

3
Debapriyo Majumder

Vous pouvez écrire votre propre désérialiseur comme indiqué dans la réponse @ cassiomolin . Mais il y a aussi une autre option. Sur la trace de la pile, nous avons DeserializationContext.weirdStringException méthode qui nous permet de fournir à nos DeserializationProblemHandler la gestion des valeurs de chaînes étranges. Voir l'exemple ci-dessous:

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import Java.io.IOException;
import Java.time.LocalDate;
import Java.time.ZonedDateTime;
import Java.time.format.DateTimeFormatter;
import Java.util.TimeZone;

public class AppJson {

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        // override default time zone if needed
        mapper.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));

        mapper.registerModule(new JavaTimeModule());
        mapper.addHandler(new DeserializationProblemHandler() {
            @Override
            public Object handleWeirdStringValue(DeserializationContext ctxt, Class<?> targetType,
                String valueToConvert, String failureMsg) {
                LocalDate date = LocalDate.parse(valueToConvert, DateTimeFormatter.ISO_DATE);
                return date.atStartOfDay(ctxt.getTimeZone().toZoneId());
            }
        });
        String json = "{\"startDate\": \"2019-07-25\"}";
        Markdown markdown = mapper.readValue(json, Markdown.class);
        System.out.println(markdown);
    }
}

Au-dessus des impressions de code:

Markdown{startDate=2019-07-25T00:00-07:00[America/Los_Angeles]}
0
Michał Ziober

Vous devez convertir votre String variable startDate en ZonedDateTimne puis elle sera convertie et enregistrée dans votre base de données ou autre.

Comme dans json startDate à venir est au format chaîne et vous avez dit que vous ne pouvez pas changer le POJO alors, vous devez le convertir avant de l'attribuer à private ZonedDateTime startDate;

Vous pouvez le faire comme dans l'exemple ci-dessous:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss a z");
ZonedDateTime dateTime = ZonedDateTime.parse("2019-03-27 10:15:30 AM +05:30", formatter);
System.out.println(dateTime);
0
Pawan Tiwari

Malheureusement, sans changer le type de POJO en LocalDate, cela serait difficile.

La solution la plus proche à laquelle je peux penser est d'écrire un JsonDeserializer personnalisé pour Jackson, ce qui n'est absolument pas une bonne pratique pour ce genre de chose.

voir https://fasterxml.github.io/jackson-databind/javadoc/2.3.0/com/fasterxml/jackson/databind/JsonDeserializer.html

0
Jordan Lefébure