Open JanC opened 2 years ago
@JanC any update on this? Did you figure out any workaround?
hey, nope :) I gave up on trying. It seems Kotlin sealed classes and openapi generation (or the other way round) is a bit problematic.
@JanC I tried the following and kinda got the result I believe you need:
@GetMapping("/test")
@ApiResponses(value = [
ApiResponse(responseCode = "200", description = "OK", content = [
(Content(mediaType = "application/json", schema = Schema(implementation = Property::class)))
])])
fun test() = Property(Attributes.TypeA("title"))
data class Property(val attributes: Attributes)
@Schema(name = "Property.Attributes", oneOf = [Attributes.TypeA::class, Attributes.TypeB::class])
sealed interface Attributes {
data class TypeA(val title: String): Attributes
data class TypeB(val name: String): Attributes
}
Not sure if the difference is that I am using a sealed interface and you a sealed class, but you could try
Hi @lluistfc,
It looks like, Kotlin sealed interface
and also sealed class
from the Swagger perspective works the same.
However, the issue is that the crucial "discriminator" property is not present in generated JSON schema, which makes it useless for code gen. See this example:
@Schema(
description = "Root model.",
subTypes = [RootModel.Child1::class, RootModel.Child2::class],
discriminatorProperty = "type", // same as default value in KotlinX Json serialization
)
@Serializable
sealed interface RootModel {
@Serializable
@SerialName("Child1")
@Schema(description = "Child1")
data class Child1(
val prop1: String,
) : RootModel
@Serializable
@SerialName("Child2")
@Schema(description = "Child2")
data class Child2(
val prop1: String,
) : RootModel
}
Please can someone direct us to a working example of Kotlin sealed
interface
/ class
(or anything) using KotlinX Serialization (so the type
property is actually not present in root model) with working discriminator property generated in JSON Schema?
Thanks
@Matej-Hlatky I think this works. I'm using jackson for serialisation, not sure if that helps you, but swagger-core uses jackson so I think its the easiest way.
//tell jackson how to serialise and deserialise with discriminator included
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(
JsonSubTypes.Type(name = "a", value = DiscriminatedOptions.OptionsA::class),
JsonSubTypes.Type(name = "b", value = DiscriminatedOptions.OptionsB::class),
)
//configure the json schema to use a discriminated oneof with the same mapping as we told jackson to use
@Schema(
required = false,
oneOf = [
DiscriminatedOptions.OptionsA::class,
DiscriminatedOptions.OptionsB::class,
],
//important that these match the subtype names in @JsonSubTypes
discriminatorMapping = [
DiscriminatorMapping(value = "a", schema = DiscriminatedOptions.OptionsA::class),
DiscriminatorMapping(value = "b", schema = DiscriminatedOptions.OptionsB::class),
])
sealed interface DiscriminatedOptions {
data class OptionsA(val property1: String) : DiscriminatedOptions
data class OptionsB(val property2: String): DiscriminatedOptions
}
@ed-curran I'm using similar method (but without class nesting) and I have a problem, that IntResult and StringResult generates allOf, which breaks generation, when used in pair with oneOf
@Schema(
discriminatorProperty = "type",
discriminatorMapping = [
DiscriminatorMapping(value = "string", schema = StringResult::class),
DiscriminatorMapping(value = "integer", schema = IntResult::class),
],
oneOf = [
StringResult::class,
IntResult::class
]
)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = false
)
@JsonSubTypes(
JsonSubTypes.Type(value = IntResult::class, name = "integer"),
JsonSubTypes.Type(value = StringResult::class, name = "string"),
)
sealed interface FeatureResult
data class StringResult(val value: String) : FeatureResult
data class IntResult(val value: Int) : FeatureResult
Resulting spec (via springdoc)
FeatureResult:
required:
- type
type: object
properties:
type:
type: string
discriminator:
propertyName: type
mapping:
string: '#/components/schemas/StringResult'
integer: '#/components/schemas/IntResult'
oneOf:
- $ref: '#/components/schemas/IntResult'
- $ref: '#/components/schemas/StringResult'
StringResult:
required:
- value
type: object
allOf:
- $ref: '#/components/schemas/FeatureResult'
- type: object
properties:
value:
type: string
IntResult:
required:
- value
type: object
allOf:
- $ref: '#/components/schemas/FeatureResult'
- type: object
properties:
value:
type: integer
format: int32
this scheme breaks generation with openapi-codegen for spring and kotlin-spring generators (maybe it's the problem of codegen tool after all)
Describe the bug Given a Kotlin sealed class annotated with
@Schema(oneOf = ..)
, swagger-core resolves a schema which contains bothtype: object
andoneOf
To Reproduce Model classes:
Unit test to reproduce:
The issue with the both
oneOf
andtype: object
being present is that other swagger libraries have to make the assumption as to how to parse such a schema.See my other ticket https://github.com/yonaskolb/SwagGen/issues/302
I wonder if the type should be simply omitted for composed schemes here https://github.com/swagger-api/swagger-core/blob/0a16eb7c9be4475e90957e3b91b6e03ace5124f6/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java#L491-L494