Фон
{
"markdownPercentage": 20,
"currency": "SEK",
"startDate": "2019-07-25"
}
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.
}
Проблема
Когда приложение пытается десериализовать сообщение в объект, оно выдает следующее исключение
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;
}
Вопрос
Я понимаю, что полученному ZonedDateTime в POJO нужен элемент «время», которого нет в исходном сообщении. У меня есть контроль только над objectMapper. Есть ли возможная конфигурация, которая может заставить это работать?
Примечание
Я в порядке, если элемент времени в десериализованном POJO «предполагается» как startOfDay, то есть «00.00.00.000Z».
Я не могу изменить POJO. Ни сообщения. Производители используют один и тот же POJO для создания сообщения.
Я предполагаю, что это работает для производителей, потому что @JsonFormat учитывается во время сериализации POJO в JSON Джексоном.
Поскольку startDate, который вы получаете из JSON, не имеет времени, я думаю, у вас не возникнет проблем с установкой времени на 00:00:00?
Правильный. В десериализованном POJO совершенно нормально, если элемент времени «предполагается» равным 00:00:00.000Z.




К сожалению, без изменения типа POJO на LocalDate это было бы сложно.
Ближайшее решение, которое я могу придумать, это написать кастомный JsonDeserializer для Джексона, что абсолютно не рекомендуется для такого рода вещей.
Ссылка мертва. Похоже на обновленный: fastxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/…
Вам нужно преобразовать переменную StringstartDate в ZonedDateTimne, после чего она будет преобразована и сохранена в вашей БД или где-то еще.
Поскольку в будущем 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);
Вопрос был об использовании класса Jackson ObjectMapper для этого. Вы не отвечаете на вопрос.
I have control only over the
ObjectMapper. Is there any possible configuration that can make this work?
Пока вас устраивают значения по умолчанию для времени и часового пояса, вы можете обойти это с помощью пользовательского десериализатора:
public class ZonedDateTimeDeserializer extends JsonDeserializer<ZonedDateTime> {
@Override
public ZonedDateTime deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext)
throws IOException {
LocalDate localDate = LocalDate.parse(
jsonParser.getText(),
DateTimeFormatter.ISO_LOCAL_DATE);
return localDate.atStartOfDay(ZoneOffset.UTC);
}
}
Затем добавьте его в модуль и зарегистрируйте модуль в своем экземпляре ObjectMapper:
SimpleModule module = new SimpleModule();
module.addDeserializer(ZonedDateTime.class, new ZonedDateTimeDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
Если добавление десериализатора в модуль вас не устраивает (в том смысле, что эта конфигурация будет применяться к другим экземплярам ZonedDateTime), вы можете положиться на миксины, чтобы определить, к каким полям будет применяться десериализатор. Сначала определите смешанный интерфейс, как показано ниже:
public interface MarkdownMixIn {
@JsonDeserialize(using = ZonedDateTimeDeserializer.class)
ZonedDateTime getDate();
}
А затем привяжите смешанный интерфейс к нужному классу:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Markdown.class, MarkdownMixIn.class);
+1 за объяснение смешанного интерфейса, чтобы избежать проблем при десериализации других полей формата JSON «дата-время».
ты не хочешь использовать ISO_LOCAL_DATE_TIME ?
Вы можете написать свой собственный десериализатор, как показано в ответе @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]}
Проблема: я хотел бы анализировать даты из json в объекты java LocalDateTime/ZonedDateTime. ZonedDateTimeSerializer существует, но ZonedDateTimeDeserializer не существует. Поэтому я создал собственный ZonedDateTimeDeserializer.
public static final String ZONED_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSz";
@Getter
@Setter
@JsonSerialize(using = ZonedDateTimeSerializer.class)
@JsonDeserialize(using = ZonedDateTimeDeserializer.class) // Doesn't exist, So I created a custom ZonedDateDeserializer utility class.
@JsonFormat(pattern = ZONED_DATE_TIME_FORMAT)
@JsonProperty("lastUpdated")
private ZonedDateTime lastUpdated;
Решение: в итоге я получил более простой код с меньшим количеством строк.
Класс полезности для десериализация и ЗонедДатеВремя:
/**
* Custom {@link ZonedDateTime} deserializer.
*
* @param jsonParser for extracting the date in {@link String} format.
* @param deserializationContext for the process of deserialization a single root-level value.
* @return {@link ZonedDateTime} object of the date.
* @throws IOException throws I/O exceptions.
*/
public class ZonedDateTimeDeserializer extends JsonDeserializer<ZonedDateTime> {
@Override
public ZonedDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
return ZonedDateTime.parse(jsonParser.getText(), DateTimeFormatter.ofPattern(ZONED_DATE_TIME_FORMAT));
}
}
Что если вместо этого вы используете Местная ДатаВремя. В этом случае все еще проще, нам уже предоставлены классы десериализатора и сериализатора. Нет необходимости в пользовательских служебных классах, как определено выше:
@Getter
@Setter
@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonFormat(pattern = ZONED_DATE_TIME_FORMAT) //Specify the format you want: "yyyy-MM-dd'T'HH:mm:ss.SSS"
@JsonProperty("created")
private LocalDateTime created;
Другие ссылки, которые также помогли в исследовании: идеи, которые привели к этому решению
Ключевые слова: формат json localDateTime zonedDateTime
к сожалению, вы не можете десериализовать формат String Object в ZonedDateTime по умолчанию. Но вы можете решить эту проблему двумя способами.
путь 01
Чтобы изменить тип ZonedDateTime на LocalDate, введите свой класс POJO и передайте значение в виде строки 03-06-2012
путь 02
Но если вам нужно сохранить дату и время с часовым поясом, вам нужно сделать следующее, чтобы преодолеть
Шаг 01
Создайте класс для десериализации ZonedDateTime с помощью DateTimeFormat
public class ZonedDateTimeDeserializer extends JsonDeserializer<ZonedDateTime> {
@Override
public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss z");
LocalDate localDate = LocalDate.parse(p.getText(),dateTimeFormatter);
return localDate.atStartOfDay(ZoneOffset.UTC);
}
}
Шаг 02
Вы должны использовать этот класс Deserialize с затронутым полем в вашем классе POJO с поддержкой аннотации уровня метода @JsonDeserialize.
@JsonDeserialize(using = ZonedDateTimeDeserializer.class)
private ZonedDateTime startDate;
Шаг 03
Передача значения в виде строки в указанном выше формате, который задается в классе ZonedDateTimeDeserializer.
"startDate" : "09-03-2003 10:15:00 Europe/Paris"
Вы имеют десериализуете прямо в этот тип? Местное свидание на самом деле совсем не то же самое, что
ZonedDateTime. В качестве альтернативы, можете ли вы изменить свойство вMarkdownнаLocalDate? В конце концов, это то, что на самом деле представляют данные.