Description
Currently I'm using OpenAPI generator only to generate Kotlin model types representing exchanged HTTP body entities.
As far as I can tell deserialization of JSON entities from HTTP request bodies works as expected; but I've recognized deviation when serializing received object graphs back to JSON in response bodies:
Duplication of JSON-field used as polymorphism discriminator.
openapi-generator version
5.3.1 (currently resolved from wildcard declaration 5.+ within build.gradle file)
OpenAPI declaration file content or url
I've simplified OpenAPI specification test-specification.yml for easier testing:
openapi: 3.0.3
info:
title: Test Case
version: 0.0.1
paths:
/dummy:
get:
responses:
200:
description: Dummy.
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
components:
schemas:
Order:
type: object
required:
- businessID
- product
properties:
businessID:
type: string
product:
$ref: '#/components/schemas/Product'
ProductTypeEnum:
type: string
enum:
- one
- two
Product:
type: object
required:
- type
- name
properties:
type:
$ref: "#/components/schemas/ProductTypeEnum"
name:
type: string
discriminator:
propertyName: type
mapping:
one: "#/components/schemas/ProductOne"
two: "#/components/schemas/ProductTwo"
ProductOne:
allOf:
- $ref: "#/components/schemas/Product"
- type: object
required:
- attribute
properties:
attribute:
type: string
ProductTwo:
allOf:
- $ref: "#/components/schemas/Product"
- type: object
required:
- capacity
properties:
capacity:
type: number
OpenAPI specification declares schemata of central entity Order having two fields, one of them polymorphic with two possible sub-types of Product: ProductOne and ProductTwo. Field type is used as discriminator.
Generation Details
Corresponding chapter from Gradle build file build.gradle looks like so:
plugins {
[...]
id('org.openapi.generator') version '5.+'
}
[...]
openApiGenerate {
generatorName = 'kotlin'
inputSpec = "${rootDir}/test-specification.yml"
outputDir = "${buildDir}/openapi-generated-test"
modelPackage = 'test'
configOptions = [
dateLibrary : 'java8',
enumPropertyNaming : 'original',
serializationLibrary: 'jackson'
]
globalProperties = [
models : '',
modelDocs: 'false'
]
}
[...]
As you can see, jackson is configured as serializationLibrary, cause I'm already using this lib for other JSON-related purposes within the project.
Steps to reproduce
package test
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import spock.lang.Specification
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
import static groovy.json.JsonOutput.prettyPrint
class TestOrderSpec extends Specification {
def JSON_MAPPER = new ObjectMapper()
.registerModules(new KotlinModule())
.setSerializationInclusion(NON_NULL)
def 'JSON deserialization/serialization round trip'() {
when:
Order deserializedOrder = JSON_MAPPER.readValue(testOrderJSON, Order)
then:
with(deserializedOrder) {
businessID == 'BID_4711'
with(product) {
type == TestProductTypeEnum.two
name == 'Name of connection'
capacity == BigDecimal.valueOf(42L)
}
}
when: 'Serialization back to JSON yields same result'
def backSerialization = JSON_MAPPER.writerFor(Order).writeValueAsString(deserializedOrder)
then:
prettyPrint(backSerialization) == prettyPrint(testOrderJSON)
where:
testOrderJSON = """{
"businessID":"BID_4711",
"product":{
"type": "two",
"name":"Name of product",
"capacity":42.0
}
}"""
}
}
Related issues/PRs
Unknown
Suggest a fix
Here is generated Kotlin code of one of the types involved (removed comments, @file:Suppress annotation, etc.):
package test
import test.Product
import test.ProductTwoAllOf
import test.ProductTypeEnum
import com.fasterxml.jackson.annotation.JsonProperty
data class ProductTwo (
@field:JsonProperty("type")
override val type: ProductTypeEnum,
@field:JsonProperty("name")
override val name: kotlin.String,
@field:JsonProperty("capacity")
val capacity: java.math.BigDecimal
) : Product
Provided Spock-driven test case uses following pretty-printed JSON to get deserialized to the generated data types (using Jackson framework):
{
"businessID":"BID_4711",
"product":{
"type": "two",
"name":"Name of product",
"capacity":42.0
}
}
Test case shows proper deserialization to the expected graph of instances, all fields contain the expected content!
But, when serializing the received object graph back to JSON, I got the following result:
{
"businessID":"BID_4711",
"product":{
"type": "two",
"type": "two",
"name":"Name of product",
"capacity":42.0
}
}
As you can see, discriminator field type appears twice!
Playing around with some options, I found a solution to remove this duplicate line by modifying generated code by hand.
Within classes ProductOne and ProductTwo, add access = JsonProperty.Access.WRITE_ONLY instruction with JsonProperty annotation on type field like so:
[...]
@field:JsonProperty("type", access = JsonProperty.Access.WRITE_ONLY)
override val type: TestProductTypeEnum,
[...]
I've not deeply tested this modification for side effects, but it reproducibly solves the problem at hand:
Deserialising JSON to an object graph and serializing it back to JSON yields exactly the same pretty-printed JSON document (pretty-printed to remove issues with line breaks, etc.).
So I suggest to modify code generator to generate shown slightly changed annotation to solve this issue.
Description
Currently I'm using OpenAPI generator only to generate Kotlin model types representing exchanged HTTP body entities.
As far as I can tell deserialization of JSON entities from HTTP request bodies works as expected; but I've recognized deviation when serializing received object graphs back to JSON in response bodies:
Duplication of JSON-field used as polymorphism discriminator.
openapi-generator version
5.3.1(currently resolved from wildcard declaration5.+withinbuild.gradlefile)OpenAPI declaration file content or url
I've simplified OpenAPI specification
test-specification.ymlfor easier testing:OpenAPI specification declares schemata of central entity
Orderhaving two fields, one of them polymorphic with two possible sub-types ofProduct:ProductOneandProductTwo. Fieldtypeis used as discriminator.Generation Details
Corresponding chapter from Gradle build file
build.gradlelooks like so:As you can see,
jacksonis configured asserializationLibrary, cause I'm already using this lib for other JSON-related purposes within the project.Steps to reproduce
Related issues/PRs
Unknown
Suggest a fix
Here is generated Kotlin code of one of the types involved (removed comments,
@file:Suppressannotation, etc.):Provided Spock-driven test case uses following pretty-printed JSON to get deserialized to the generated data types (using Jackson framework):
Test case shows proper deserialization to the expected graph of instances, all fields contain the expected content!
But, when serializing the received object graph back to JSON, I got the following result:
As you can see, discriminator field
typeappears twice!Playing around with some options, I found a solution to remove this duplicate line by modifying generated code by hand.
Within classes
ProductOneandProductTwo, addaccess = JsonProperty.Access.WRITE_ONLYinstruction withJsonPropertyannotation ontypefield like so:I've not deeply tested this modification for side effects, but it reproducibly solves the problem at hand:
Deserialising JSON to an object graph and serializing it back to JSON yields exactly the same pretty-printed JSON document (pretty-printed to remove issues with line breaks, etc.).
So I suggest to modify code generator to generate shown slightly changed annotation to solve this issue.