Cannot deserialize value of type `java.time.OffsetDateTime` from String in openapi client
Asked Answered
S

2

10

I have a spring boot application with a java client generated via the gradle plugin:

openApiGenerate {
    generatorName = "java"
    inputSpec = specsYml
    outputDir = "$buildDir/generated".toString()
    apiPackage = "com.customapi.api"
    invokerPackage = "com.customapi.invoker"
    modelPackage = "com.customapi.model"
    configOptions = [
        dateLibrary: "java8",
        library    : "resttemplate"
    ]
}

I have chosen "java8" as dateLibrary as that seems to be the preferred one for a project with java 1.8.

With that generated client I'm executing a request that returns an object that holds a timestamp. I get the following error:

java.lang.IllegalStateException: Failed to execute CommandLineRunner
    ...
Caused by: org.springframework.web.client.RestClientException: Error while extracting response for type [class com.customapi.model.Info] and content type [application/json];
    ...
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.time.OffsetDateTime` from String "2020-07-21T12:12:23.000+0200": ...
   ...
...
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.OffsetDateTime` from String "2020-07-21T12:12:23.000+0200": Failed to deserialize java.time.OffsetDateTime: (java.time.format.DateTimeParseException) Text '2020-07-21T12:12:23.000+0200' could not be parsed at index 23
 at [Source: (ByteArrayInputStream); line: 1, column: 84] (through reference chain: com.customapi.model.Info["buildTimestamp"])
    at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1679) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:935) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.datatype.jsr310.deser.JSR310DeserializerBase._handleDateTimeException(JSR310DeserializerBase.java:86) ~[jackson-datatype-jsr310-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:218) ~[jackson-datatype-jsr310-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:50) ~[jackson-datatype-jsr310-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:369) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4218) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3267) ~[jackson-databind-2.10.3.jar:2.10.3]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:269) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    ... 17 common frames omitted
Caused by: java.time.format.DateTimeParseException: Text '2020-07-21T12:12:23.000+0200' could not be parsed at index 23
    at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949) ~[na:1.8.0_151]
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1777) ~[na:1.8.0_151]
    at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:212) ~[jackson-datatype-jsr310-2.10.3.jar:2.10.3]
    ... 24 common frames omitted

The relevant parts of the Info class in question:

...
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2020-07-26T14:09:54.137+02:00[Europe/Berlin]")
public class Info {
    ...
    public static final String JASON_PROPERTY_BUILD_TIMESTAMP = "buildTimestamp";
    private OffsetDateTime buildTimestamp;
    ...
    public Info buildTimestamp(OffsetDateTime buildTimestamp) {
        this.buildTimestamp = buildTimestamp;
        return this;
    }

    public void setBuildTimestamp(OffsetDateTime buildTimestamp) {
        this.buildTimestamp = buildTimestamp;
    }
    ...
}

Both setter methods accept OffsetDateTime objects and have no annotations so the conversion must happen elsewhere. The input String again is "2020-07-21T12:12:23.000+0200". relevant dependencies are

ext {
    swagger_annotations_version = "1.5.22"
    jackson_version = "2.10.3"
    jackson_databind_version = "2.10.3"
    jackson_databind_nullable_version = "0.2.1"
}

dependencies {
    compile "io.swagger:swagger-annotations:$swagger_annotations_version"
    compile "com.fasterxml.jackson.core:jackson-core:$jackson_version"
    compile "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
    compile "com.fasterxml.jackson.core:jackson-databind:$jackson_databind_version"
    compile "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jackson_version"
    compile "org.openapitools:jackson-databind-nullable:$jackson_databind_nullable_version"
    compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version"
}

There seem to be a lot of problems with jackson and java 8 and most solutions on this site seem to be adding annotations. But I doubt that modifying generated code is the proper solution. Did I overlook an important parameter when generating the client? Does the server supply the wrong format? How can I investigate this?

Update:

When I switch dateLibrary to legacy it works, so I think I receive the correct data.

There is a bug in the (jaxrs) server generator https://github.com/swagger-api/swagger-codegen/issues/3648#issuecomment-244056314 that has the server send out wrongly formatted (without colon) date-time. My solution was to use the legacy dateLibrary for the client which can handle the wrong format.

Speaks answered 27/7, 2020 at 10:23 Comment(3)
An observation: That is not a valid string to be parsed by OffsetDateTime.parse() because the default datetime format expects the offset to have a colon in it: +02:00. So, this works: OffsetDateTime.parse("2020-07-21T12:12:23.000+02:00") reference. I assume Jackson must have an annotation to specify the format - "yyyy-MM-dd'T'HH:mm:ss.SSSxx" where xx means the offset without a colon.Ranite
@andrewjames Thank you so much for the hint! The server does indeed send out the wrong format, despite being generated from the same specification.Speaks
I'm having exactly the same issue. As in your case, creating my own objectMapper and passing it to restTemplate works to parse OffsetDateTime when the library in the POM is set to legacy. I don't want to use the legacy library! But the java8 one. Curiously I'm still struggling to put the java8 library to work. Any ideas on that matter? My dateTime string has the shape: 2011-09-06T17:40:00Jongjongleur
R
14

Following my comment in the question, I realize you don't need a Jackson annotation. You just need to adjust the setter. Here is a basic demo:

Assume the following class:

import java.time.OffsetDateTime;
//import com.fasterxml.jackson.annotation.JsonSetter;
import java.time.format.DateTimeFormatter;

public class MyOdt {
    
    private OffsetDateTime odt;

    public OffsetDateTime getOdt() {
        return odt;
    }

    //@JsonSetter("odt")
    public void setOdt(String odtString) {
        final String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSxx";
        DateTimeFormatter dtfB = DateTimeFormatter.ofPattern(pattern);
        this.odt = OffsetDateTime.parse(odtString, dtfB);
    }
    
}

The class will be created from a JSON fragment such as this:

String jsonTest = "{ \"odt\" : \"2020-07-21T12:12:23.000+0200\" }";

The object mapper:

ObjectMapper objectMapper = new ObjectMapper()
        .registerModule(new JavaTimeModule())
        .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

MyOdt odtTest = objectMapper.readValue(jsonTest, MyOdt.class);

For reference, here is the original comment in the question:

An observation: That is not a valid string to be parsed by OffsetDateTime.parse() because the default datetime format expects the offset to have a colon in it: +02:00. So, this works: OffsetDateTime.parse("2020-07-21T12:12:23.000+02:00")

Ranite answered 27/7, 2020 at 14:43 Comment(0)
R
2

I was helped by switching dateLibrary to java8-localdatetime

See also here.

Republicanize answered 3/8, 2021 at 11:46 Comment(1)
yes it worked by changing the dateLibrary . ThanksTermite

© 2022 - 2024 — McMap. All rights reserved.