openapi springboot generator jackson no String-argument constructor/factory method to deserialize from String value
Asked Answered
P

5

26

I'm using OpenApi SpringBoot generator to generate controller interfaces and models. This creates model classes with JsonNullable<String> for nullable fields. However I'm getting a Jackson type definition error while POST request is sent with value present in a nullable field.

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.openapitools.jackson.nullable.JsonNullable` (no Creators, like default construct, exist): no String-argument constructor/factory method to deserialize from String value ('TG')
 at [Source: (PushbackInputStream); line: 3, column: 19] (through reference chain: com.example.rest.CreateRequest["displayName"])
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1452) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1028) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:371) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:323) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1373) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:171) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:161) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:127) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:369) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013) ~[jackson-databind-2.9.7.jar:2.9.7]
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3084) ~[jackson-databind-2.9.7.jar:2.9.7]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:239) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:227) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:204) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:157) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:130) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:126) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]

Same thing happens for Integer or any other type. It works if the request contains only non-nullable fields.

Any idea what is going wrong here ?

Pyroclastic answered 30/12, 2019 at 0:6 Comment(0)
N
36

OpenAPI Generator team implemented jackson-databind-nullable module which you should include to your project. The newest version is 0.2.6.

<dependency>
    <groupId>org.openapitools</groupId>
    <artifactId>jackson-databind-nullable</artifactId>
    <version>0.2.6</version>
</dependency>

If module will not be detected automatically you need to do it manually by:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JsonNullableModule());

Or if you using Jackson in Spring project, then you can register it by:

@Bean
@Primary
public Jackson2ObjectMapperBuilder customObjectMapper() {
    return new Jackson2ObjectMapperBuilder()
            // other configs are possible
            .modules(new JsonNullableModule());
}
Neldanelia answered 30/12, 2019 at 18:48 Comment(8)
Thank You !!! I registered the module like this in a @Configuration class. @Autowired void configureObjectMapper(final ObjectMapper mapper) { mapper.registerModule(new JsonNullableModule()); }Pyroclastic
doesn't solve the issue for me :( spring boot 2.3.3Lilylilyan
@m02ph3u5, are you sure, JsonNullableModule is properly registered in your case?Circinus
I don't think so. I've added a configuration for the object mapper but I think that the generated code won't use this configured OM. Haven't found a way to inject the mapper yetLilylilyan
@Lilylilyan Did you find a solution? I have the same issue while the JsonNullableModule is registered.Enclosure
@Enclosure sadly no, not usable atm with gradle and webclient or resttemplate github.com/OpenAPITools/openapi-generator/issues/… I did 90% manually, nowLilylilyan
Me neither. I switched to okhttp-gson library which I managed to get working.Enclosure
@Clouren, @Lilylilyan - please create new question with example JSON payload and POJO class. Also, add information such as: libraries version, exception stack trace, etc. and point a link here in the comment section to this new question. Someone will help you with this issue.Circinus
I
24

Since openapi-generator v5.1.1 you can use in your config.json:

{
  "openApiNullable": false
}

To remove that dependency. Most of the time it's not needed (if you check the generated code), so it's cool to be able to not add an unwanted dependency in your class path.

Or directly in the gradle plugin in your build.gradle you can do:

openApiGenerate {
    generatorName = "spring"
    inputSpec = "$rootDir/specs/petstore-v3.0.yaml"
    outputDir = "$buildDir/generated"
    apiPackage = "org.openapi.example.api"
    modelPackage = "org.openapi.example.model"
    configOptions = [
        openApiNullable: "false"
    ]
}
Impale answered 5/7, 2021 at 19:16 Comment(1)
Simple and clean solution! It generates: private Boolean myField = null; instead of pesky private JSonNullable<Boolean> myField = JSonNullable.undefined();Rend
I
6

As @Michał Ziober said, you have to add jackson-databind-nullable to your maven dependencies and need to register the jackson module. The easiest way though is to add the following to your application:

@Configuration
public class JacksonConfig {
  @Bean
  public Module jsonNullableModule() {
    return new JsonNullableModule();
  }
}

Spring automatically adds Jackson Modules to the ObjectMapper, as the docs say:

Any beans of type com.fasterxml.jackson.databind.Module are automatically registered with the auto-configured Jackson2ObjectMapperBuilder and are applied to any ObjectMapper instances that it creates. This provides a global mechanism for contributing custom modules when you add new features to your application.

Istria answered 19/3, 2021 at 14:22 Comment(0)
D
0

Solution working for me (in addition to the self-generated code from openapi yaml):

<dependency>
    <groupId>org.openapitools</groupId>
    <artifactId>jackson-databind-nullable</artifactId>
    <version>0.2.1</version>
</dependency>

Then in @Configuration class/es:

@Bean
public JsonNullableModule jsonNullableModule() {
    return new JsonNullableModule();
}

and

@Bean
public RestTemplate restTemplate(ObjectMapper objectMapper) {
    var converter = new MappingJackson2HttpMessageConverter();
    converter.setObjectMapper(objectMapper);
    return new RestTemplateBuilder().additionalMessageConverters(converter).build();
}
Doe answered 13/2, 2022 at 17:22 Comment(0)
A
0

This is the only solution which worked for me and which does not destroy the post added serializer modules in ObjectMapper

@Configuration
public class WebApplicationConfig implements WebMvcConfigurer {
    
    @Bean
    public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
        return new MappingJackson2HttpMessageConverter(objectMapper());
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new StringHttpMessageConverter());
        converters.add(jackson2HttpMessageConverter());
        converters.add(new ByteArrayHttpMessageConverter());
    }

    public ObjectMapper objectMapper() {
        val mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        mapper.registerModule(new JsonNullableModule());
        mapper.registerModule(new JavaTimeModule());
        mapper.registerModule(new Jdk8Module());
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return mapper;
    }

}
Authorize answered 28/3, 2022 at 12:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.