web-dev-qa-db-ja.com

ObjectMapperを使用してJSON日付形式をZonedDateTimeに逆シリアル化する

背景

  1. 次のJSONがあります(Kafkaからのメッセージ)
{
      "markdownPercentage": 20,
      "currency": "SEK",
      "startDate": "2019-07-25"
}
  1. 次の(JSONスキーマが生成された)POJOを持っています(会社の共有リソースであるため、POJOを変更できません)
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. 私たちのアプリケーションは、Spring Cloud Streamを使用してKafkaからJSONメッセージ(1)を読み取り、POJO(2)を使用し、それを使って処理を行うSpring Bootアプリケーションです。

問題

アプリケーションがメッセージをオブジェクトに逆シリアル化しようとすると、次の例外がスローされます

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

現在のコード

次のobjectMapperを定義しています

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

質問

POJOの結果のZonedDateTimeには、ソースメッセージには存在しない「time」要素が必要であることを理解しています。私はobjectMapperのみを制御できます。これを機能させる可能性のある構成はありますか?

逆シリアル化されたPOJOの時間要素がstartOfDayであると「想定」されている場合、つまり「00.00.00.000Z」であれば問題ありません。

3

@ cassiomolin に示すように、独自のデシリアライザを作成できます。しかし、別のオプションもあります。スタックトレースにはDeserializationContext.weirdStringExceptionメソッドを使用して、奇妙な文字列値の処理をDeserializationProblemHandlerに提供できます。以下の例を参照してください:

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

上記のコードプリント:

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

String変数startDateZonedDateTimneに変換する必要があります。変換するとDBに保存されます。

来るjsonのstartDateは文字列形式であり、POJOを変更できないと述べたので、private ZonedDateTime startDate;に割り当てる前に変換する必要があります。

以下の例のように行うことができます。

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

悲しいことに、POJOのタイプをLocalDateに変更しないと、それは難しいでしょう。

私が考えることができる最も近い解決策は、ジャクソンのカスタムJsonDeserializerを記述することです。

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

0