OpenAPI Generator Kotlin Jackson - kotlin

I use the openapi generator kotlin module to generate kotlin classes from my openapi.yaml file. The process works fine until I try to deserialize the received JSON in my code to a kotlin class using Jackson.
This is the generated class
data class Request (
#field:JsonProperty("name")
var name: kotlin.String,
)
This is the error I get
java.lang.IllegalArgumentException: Cannot construct instance of `...package.Request` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: UNKNOWN; byte offset: #UNKNOWN]
I noticed that when I remove the "#field:" part in the generated code, then everything works like a charm.
So now my question is can I either remove the #field from the generator or make Jackson deserialize it correctly?
The versions that I use are
jackson: 2.13.1
open-api-generator (gradle plugin): 5.3.0

I had the same error and registering the Kotlin Jackson module fixed it for me: https://github.com/FasterXML/jackson-module-kotlin

Related

How to automatically convert from org.apache.camel.converter.stream.InputStreamCache to Pojo using Jackson in a Spring Boot Camel Kotlin application

In a Spring Boot 2.7. Camel 3.20.x project written in Kotlin I have a REST endpoint that receives a JSON payload. I've added the Camel Jackson dependency to deal with JSON<->POJO transformation:
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-jackson-starter</artifactId>
<version>${camel.version}</version>
</dependency>
data class Payment(val iban: String, val amount: Float)
rest("/payments")
.post("/")
.to("direct:processPayment")
from("direct:processPayment")
.log("Body \${body}")
.log("Body \${body.getClass()}")
These are the logs of the route:
Body {"payment":{"iban":"ABCD","amount":150.0}}
Body class org.apache.camel.converter.stream.InputStreamCache
As you can see the body is correctly displayed as String, however, the type is InputStreamCache instead of my Payment DTO.
I updated the route to unmarshall the body to the Payment DTO:
from("direct:processPayment")
.unmarshal().json(JsonLibrary.Jackson, Payment::class.java)
.log("Body \${body}")
.log("Body \${body.getClass()}")
This fails with:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `xxx.Payment` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
Why isn't the conversion working?
The encountered error has nothing to see with Camel nor Spring, but is directly related to Jackson.
As the error message indicates it, the reason is that the Payment pojo does not satisfy the requirement of having a default (parameterless) constructor.
The source of the problem was correctly described in the accepted answer. However there's a Kotlin compatible solution that allows the use of data classes without default (empty) constructors.
Add jackson-module-kotlin dependency:
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
With this, Spring Boot will tweak its own Jackson ObjectMapper. Now, just tell Camel to use that Spring configured object mapper:
camel:
dataformat:
jackson:
auto-discover-object-mapper: true

Kotlin Client Generator support for Enum Unknown Values

I'm wondering if there is a way to generate a kotlin lib from an openapi spec that will support a default value if it doesn't serialize a known enum value.
Ex: the Java Generator has the enumUnknownDefaultCase flag in its spec
Ref from the kotlin generator seems to read in the config for the moshi serializer here
But unable to get this working by specifying the following two options via the kotlin generator:
"moshiCodeGen": "true"
"enumUnknownDefaultCase": "true"
Related conversation here
Thanks for any help figuring out how to get kotlin clients to not explode on new enumeration values.
If you are using openapi-generator-cli to generate code, the option enumUnknownDefaultCase should working by adding --additional-properties=enumUnknownDefaultCase=true
Example:
java -jar openapi-generator-cli-6.0.0.jar -i "example.com/swagger?format=openapi" -g kotlin --library=jwm-retrofit2 -o my-api-sdk --additional-properties=enumUnknownDefaultCase=true
It will create a SerializerHelper class which will add additional enum-adapter to Moshi builder like this
moshiBuilder
.add(MyEnum::class.java, EnumJsonAdapter.create(MyEnum::class.java).withUnknownFallback(MyEnum.unknown_default_open_api))
.add(...)
But it's not done yet, when initialize your ApiClient, you must force it to use MoshiBuilder with the enum-adapter, by default it won't use that one.
Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(ScalarsConverterFactory.create())
++ .addConverterFactory(MoshiConverterFactory.create(Serializer.moshi))
-- .addConverterFactory(MoshiConverterFactory.create(serializerBuilder.build()))
.apply {
if (converterFactory != null) {
addConverterFactory(converterFactory)
}
}

Intellij IDEA returns different results with vs. without enabled REPL for Kotlin

I create a Kotlin scratch file in Intellij IDEA and use my current project's module classpath in order to access all libraries of the project (i'm using Jackson in this example)
In both scenarios I have declared the following class:
class Test(var first: String = "a", var second: String = "b")
Without REPL enabled
val jsonAsString = "{\"first\": \"a\", \"second\":\"b\"}"
println(ObjectMapper().readValue(jsonAsString, Test::class.java).first) // prints out "a"
"a" is printed out as expected
With REPL enabled the ObjectMapper.readValue() throws the following exception
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `Line_2$Test` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"first": "a", "second":"b"}"; line: 1, column: 2]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1904)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1349)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1415)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:351)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:184)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3629)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3597)
I reproduced the bug and created an issue - https://youtrack.jetbrains.com/issue/KTIJ-21598/Scratch-REPL:-%22InvalidDefinitionException:-Cannot-construct-inst. Feel free to follow it.

jackson-dataformat-csv: cannot serialize LocalDate

When I try to serialize object containing Local date, I get following error:
csv generator does not support object values for properties
I have JSR-310 module enabled, with WRITE_DATES_AS_TIMESTAMPS and I can convert the same object to JSON without problem.
For now I resorted to mapping the object to another, string only object, but it's decadent and wasteful.
Is there a way for Jackson csv mapper to acknowledge localDates? Should I somehow enable JSR-310 specifically for csv mapper?
I had the same problem because of configuring mapper after schema. Make sure you are using the latest verson of jackson and its modules. This code works for me:
final CsvMapper mapper = new CsvMapper();
mapper.findAndRegisterModules();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); //Optional
final CsvSchema schema = mapper.schemaFor(PojoWithLocalDate.class);
// Use this mapper and schema as you need to: get readers, writers etc.
No additional annotations needed in Pojo class.

Jackson missing colon and value in JSON after key

I'm trying to chase a bug where Jackson serializes some class ApiDeclaration { String apiVersion ... private JSONObject models } as {"apiVersion":"1.0.0", ..., "models"}" - note that "models" is a JSON key with the ':' and value missing!
This is in app where this worked before, and it's possible that a recent upgrade from Jackson 2.3.2 -> 2.7.4 may have broken this for us.
Does application code have to something particular for a "mixed bean" (i.e. a static Java class with some 'normal' fields but also a JSONObject) ?