Jelly
Jelly

Reputation: 1296

LocalDateTime field: MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token

I have a main class:

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class ContentModel implements Serializable {
    @JsonProperty("serviceData")
     private ServiceData serviceData;
}

And nested-class:

 @NoArgsConstructor
 @AllArgsConstructor
 @Data
 @Builder
 public class ServiceData implements Serializable {
    @JsonProperty ("dateTimeMessageSend")
    private LocalDateTime dateTimeMessageSend;
 }

I'm trying to deserialize this by using custom deserialiser:

public class LocalDateTimeDeserializer extends StdDeserializer<LocalDateTime> {
   private static final long serialVersionUID = 1L;
   protected LocalDateTimeDeserializer() {
        super(LocalDateTime.class);
    }

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

This is, how I configure my objectMapper:

  JavaTimeModule javaTimeModule = new JavaTimeModule();
    javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
    this.objectMapper.registerModule(javaTimeModule);
    this.objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    this.objectMapper.registerModule(new Jdk8Module());
    this.objectMapper.findAndRegisterModules();
 

Application receive outside the following json:

{
 "serviceData": {
    "dateTimeMessageSend": {
       "month": "APRIL",
       "dayOfYear": 109,
       "dayOfWeek": "MONDAY",
       "nano": 183251000,
       "year": 2021,
       "monthValue": 4,
       "dayOfMonth": 19,
       "hour": 14,
       "minute": 52,
       "second": 44,
       "chronology": {
          "calendarType": "iso8601",
          "id": "ISO"
         }
      },
    }
 }

At the time of LocalDateTime-deserialization, I receive exception:

 MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token

I suppose, that jackson recognise "dateTimeMessageSend" as an object and unable parse it as a string.

What the way to solve the problem?

  1. Modify source json, for example by change LocalDateFormat to String. (What is undesirable)
  2. Futher customization of objectMapper or/and LocalDateTimeDeserializer

Upvotes: 0

Views: 1136

Answers (1)

Thomas Fritsch
Thomas Fritsch

Reputation: 10127

There seems to be some misunderstanding about what you actually receive in the deserialize method when Jackson calls your LocalDateTimeSerializer.

You do not just get a single long string like { "month": "APRIL", "dayOfYear": 109, ... }. Therefore you cannot just simply call

jp.readValueAs(String.class);

You got a MismatchedInputExcpetion here, because the parser encountered a { instead of a string "something".

Actually you get a stream of tokens (beginning with {) which you are supposed to consume up to the closing } token, extract the relevant parts from this, and build a LocalDateTime object from these parts.

See for example Baeldung - Getting started with deserialization in Jackson, especially the custom deserializer example given there. Following this tuturial you can implement the deserialize method like this:

@Override
public LocalDateTime deserialize(final JsonParser jp, final DeserializationContext ctxt)
         throws IOException {
    JsonNode node = jp.getCodec().readTree(jp);
    int year = ((NumericNode) node.get("year")).asInt();
    int monthValue = ((NumericNode) node.get("monthValue")).asInt();
    int dayOfMonth = ((NumericNode) node.get("dayOfMonth")).asInt();
    int hour = ((NumericNode) node.get("hour")).asInt();
    int minute = ((NumericNode) node.get("minute")).asInt();
    int second = ((NumericNode) node.get("second")).asInt();
    return LocalDateTime.of(year, monthValue, dayOfMonth, hour, minute, second);
}

Note that there is quite a bit of redundancy in the received JSON tree representing the LoclDateTime. For example: it contains "month": "APRIL" and "monthValue": 4. Therefore you don't need to extract all the values from the tree, but only some them.

Upvotes: 1

Related Questions