Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,22 @@ import kotlinx.serialization.encoding.Encoder
{{/enumUnknownDefaultCase}}
{{^enumUnknownDefaultCase}}
{{#generateOneOfAnyOfWrappers}}
{{#discriminator}}
import kotlinx.serialization.KSerializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
{{/discriminator}}
{{/generateOneOfAnyOfWrappers}}
{{/enumUnknownDefaultCase}}
{{#generateOneOfAnyOfWrappers}}
{{#discriminator}}
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonEncoder
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonPrimitive
{{#discriminator}}
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
{{/discriminator}}
Expand Down Expand Up @@ -100,13 +100,17 @@ import java.io.IOException
{{#discriminator}}
@Serializable(with = {{classname}}Serializer::class)
{{/discriminator}}
{{^discriminator}}
{{#serializableModel}}@KSerializable(with = {{classname}}.{{classname}}Serializer::class){{/serializableModel}}{{^serializableModel}}@Serializable(with = {{classname}}.{{classname}}Serializer::class){{/serializableModel}}
{{/discriminator}}
{{/generateOneOfAnyOfWrappers}}
{{/kotlinx_serialization}}
{{#isDeprecated}}
@Deprecated(message = "This schema is deprecated.")
{{/isDeprecated}}
{{>additionalModelTypeAnnotations}}
{{#kotlinx_serialization}}
{{#discriminator}}
{{#nonPublicApi}}internal {{/nonPublicApi}}{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}sealed interface {{classname}} {
{{#discriminator.mappedModels}}
@JvmInline
Expand Down Expand Up @@ -150,6 +154,78 @@ import java.io.IOException
}
}
}
{{/discriminator}}
{{^discriminator}}
{{#nonPublicApi}}internal {{/nonPublicApi}}{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}data class {{classname}}(var actualInstance: Any? = null) {

{{#nonPublicApi}}internal {{/nonPublicApi}}{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}object {{classname}}Serializer : KSerializer<{{classname}}> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("{{classname}}") {
element("type", JsonPrimitive.serializer().descriptor)
element("actualInstance", JsonElement.serializer().descriptor)
}

override fun serialize(encoder: Encoder, value: {{classname}}) {
val jsonEncoder = encoder as? JsonEncoder ?: throw SerializationException("{{classname}} can only be serialized with Json")

when (val instance = value.actualInstance) {
{{#composedSchemas}}
{{#oneOf}}
{{#isPrimitiveType}}
{{#isString}}
is kotlin.String -> jsonEncoder.encodeString(instance)
{{/isString}}
{{#isBoolean}}
is kotlin.Boolean -> jsonEncoder.encodeBoolean(instance)
{{/isBoolean}}
{{#isInteger}}
{{^isLong}}
is kotlin.Int -> jsonEncoder.encodeInt(instance)
{{/isLong}}
{{/isInteger}}
{{#isLong}}
is kotlin.Long -> jsonEncoder.encodeLong(instance)
{{/isLong}}
{{#isNumber}}
{{#isDouble}}
is kotlin.Double -> jsonEncoder.encodeDouble(instance)
{{/isDouble}}
{{#isFloat}}
is kotlin.Float -> jsonEncoder.encodeFloat(instance)
{{/isFloat}}
{{/isNumber}}
{{/isPrimitiveType}}
{{^isPrimitiveType}}
is {{{dataType}}} -> jsonEncoder.encodeSerializableValue({{{dataType}}}.serializer(), instance)
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Generated code uses {{{dataType}}}.serializer() for non‑primitive oneOf types, which breaks for generic/array types (e.g., List<String>.serializer() is invalid Kotlin). This causes compile errors for oneOf schemas containing arrays or other generics.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/kotlin-client/oneof_class.mustache, line 198:

<comment>Generated code uses `{{{dataType}}}.serializer()` for non‑primitive oneOf types, which breaks for generic/array types (e.g., `List<String>.serializer()` is invalid Kotlin). This causes compile errors for oneOf schemas containing arrays or other generics.</comment>

<file context>
@@ -150,6 +154,78 @@ import java.io.IOException
+                {{/isNumber}}
+                {{/isPrimitiveType}}
+                {{^isPrimitiveType}}
+                is {{{dataType}}} -> jsonEncoder.encodeSerializableValue({{{dataType}}}.serializer(), instance)
+                {{/isPrimitiveType}}
+                {{/oneOf}}
</file context>
Fix with Cubic

{{/isPrimitiveType}}
{{/oneOf}}
{{/composedSchemas}}
null -> jsonEncoder.encodeJsonElement(JsonNull)
else -> throw SerializationException("Unknown type in actualInstance: ${instance::class}")
}
}

override fun deserialize(decoder: Decoder): {{classname}} {
val jsonDecoder = decoder as? JsonDecoder ?: throw SerializationException("{{classname}} can only be deserialized with Json")
val jsonElement = jsonDecoder.decodeJsonElement()

val errorMessages = mutableListOf<String>()

{{#composedSchemas}}
{{#oneOf}}
try {
val instance = jsonDecoder.json.decodeFromJsonElement<{{{dataType}}}>(jsonElement)
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing import for kotlinx.serialization.json.decodeFromJsonElement causes generated code to fail to compile.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/kotlin-client/oneof_class.mustache, line 216:

<comment>Missing import for kotlinx.serialization.json.decodeFromJsonElement causes generated code to fail to compile.</comment>

<file context>
@@ -150,6 +154,78 @@ import java.io.IOException
+            {{#composedSchemas}}
+            {{#oneOf}}
+            try {
+                val instance = jsonDecoder.json.decodeFromJsonElement<{{{dataType}}}>(jsonElement)
+                return {{classname}}(actualInstance = instance)
+            } catch (e: Exception) {
</file context>
Fix with Cubic

return {{classname}}(actualInstance = instance)
} catch (e: Exception) {
errorMessages.add("Failed to deserialize as {{{dataType}}}: ${e.message}")
}
{{/oneOf}}
{{/composedSchemas}}

throw SerializationException("Cannot deserialize {{classname}}. Tried: ${errorMessages.joinToString(", ")}")
}
}
}
{{/discriminator}}
{{/kotlinx_serialization}}
{{^kotlinx_serialization}}
{{#nonPublicApi}}internal {{/nonPublicApi}}{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}data class {{classname}}(var actualInstance: Any? = null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,40 @@ public void polymorphicKotlinxSerialization() throws IOException {
TestUtils.assertFileContains(birdKt, "@SerialName(value = \"BIRD\")");
}

@Test(description = "generate oneOf wrapper with primitive types using kotlinx_serialization")
public void oneOfPrimitiveKotlinxSerialization() throws IOException {
File output = Files.createTempDirectory("test").toFile();
output.deleteOnExit();

final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("kotlin")
.setLibrary("jvm-retrofit2")
.setAdditionalProperties(new HashMap<>() {{
put(CodegenConstants.SERIALIZATION_LIBRARY, "kotlinx_serialization");
put("generateOneOfAnyOfWrappers", true);
}})
.setInputSpec("src/test/resources/3_0/issue_19942.json")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));

final ClientOptInput clientOptInput = configurator.toClientOptInput();
DefaultGenerator generator = new DefaultGenerator();
generator.opts(clientOptInput).generate();

final Path oneOfModelKt = Paths.get(output + "/src/main/kotlin/org/openapitools/client/models/ObjectWithComplexOneOfId.kt");
// generates data class with actualInstance (not empty sealed interface)
TestUtils.assertFileContains(oneOfModelKt, "data class ObjectWithComplexOneOfId");
TestUtils.assertFileContains(oneOfModelKt, "var actualInstance: Any?");
// has a custom KSerializer
TestUtils.assertFileContains(oneOfModelKt, "object ObjectWithComplexOneOfIdSerializer : KSerializer<ObjectWithComplexOneOfId>");
// serializer handles primitive types
TestUtils.assertFileContains(oneOfModelKt, "is kotlin.String -> jsonEncoder.encodeString(instance)");
// serializer handles deserialization via try-each
TestUtils.assertFileContains(oneOfModelKt, "decodeFromJsonElement<kotlin.String>(jsonElement)");
// parent model references the oneOf wrapper type
final Path parentModelKt = Paths.get(output + "/src/main/kotlin/org/openapitools/client/models/ObjectWithComplexOneOf.kt");
TestUtils.assertFileContains(parentModelKt, "val id: ObjectWithComplexOneOfId?");
}

@Test(description = "generate polymorphic jackson model")
public void polymorphicJacksonSerialization() throws IOException {
File output = Files.createTempDirectory("test").toFile();
Expand Down
Loading