[BUG][KOTLIN-SERVER] typeInfoAnnotation.mustache uses As.PROPERTY → discriminator property serialized twice
Los mantenedores suelen responder en 1 día
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 62/100
Línea de trabajo
La anotación se renderiza desde modules/openapi-generator/src/main/resources/kotlin-server/typeInfoAnnotation.mustache, que establece el modo include de JsonTypeInfo en As.PROPERTY. Empieza generando la especificación en el informe con kotlin-server y library jaxrs-spec, y luego serializa una instancia de Cat con Jackson estándar para confirmar la clave kind duplicada. Se considera terminado cuando la salida contiene kind una sola vez, el discriminador sigue leyéndose correctamente al deserializar y el cambio no rompe las pruebas existentes del sample de kotlin-server.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Bug Report Checklist
- Have you provided a full/minimal spec to reproduce the issue?
- Have you validated the input using an OpenAPI validator?
- Have you tested with the latest master to confirm the issue still exists?
- Have you searched for related issues/PRs?
- What's the actual output vs expected output?
- [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
For kotlin-server with library=jaxrs-spec, a oneOf schema with a discriminator whose propertyName is also declared as a required property on every subtype produces a Jackson model where the discriminator key is written twice in the serialized JSON.
Root cause: kotlin-server/typeInfoAnnotation.mustache renders
@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "...", visible = true)
on the polymorphic interface/base class. As.PROPERTY tells Jackson to inject the discriminator value during serialization. But because the subtype also declares a real Kotlin property with the same name (which is how openapi-generator always models a discriminator that is "visible"/present in properties), Jackson serializes that field too — once via @JsonTypeInfo, once as a normal @JsonProperty. The result is a JSON object with the same key appearing twice, e.g.:
{"item":{"kind":"CAT","kind":"CAT","lives":9}}
This is the same underlying defect already reported for kotlin-client/kotlin-spring in #21733, but the templates for kotlin-server (at least with library=jaxrs-spec) share the same typeInfoAnnotation partial and are affected the same way, with visible=true on the discriminator. Changing As.PROPERTY to As.EXISTING_PROPERTY fixes it (Jackson then writes the value using the existing property instead of injecting an additional one) without touching JsonSubTypes or any other part of the template.
openapi-generator version
7.26.0 (CLI jar), not yet verified against latest master.
OpenAPI declaration file content or url
openapi: 3.0.3
info:
title: Minimal oneOf discriminator repro
version: 1.0.0
paths:
/orders:
post:
operationId: createOrder
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
responses:
'201':
description: Created
components:
schemas:
Order:
type: object
required: [item]
properties:
item:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
discriminator:
propertyName: kind
mapping:
CAT: '#/components/schemas/Cat'
DOG: '#/components/schemas/Dog'
Cat:
type: object
required: [kind]
properties:
kind:
type: string
enum: [CAT]
lives:
type: integer
Dog:
type: object
required: [kind]
properties:
kind:
type: string
enum: [DOG]
barks:
type: boolean
Note: with stock kotlin-server + library=jaxrs-spec, the oneOf template (oneof_model.mustache) currently only emits typealias Order_item = Any and drops the @JsonTypeInfo/@JsonSubTypes annotations entirely (separate gap — see "Related issues" below), so to actually observe @JsonTypeInfo being applied to the interface, a one-line custom oneof_model.mustache override is needed:
{{#-first}}
{{#discriminator}}
{{>typeInfoAnnotation}}
interface {{classname}}
{{/discriminator}}
{{^discriminator}}
typealias {{classname}} = Any
{{/discriminator}}
{{/-first}}
This override exists in our project precisely to get a real interface + annotations out of oneOf with jaxrs-spec; the duplicate-key bug is in the upstream typeInfoAnnotation.mustache partial that it includes, not in this override.
Generation Details
java -jar openapi-generator-cli-7.24.0.jar generate \
-g kotlin-server --library jaxrs-spec \
-i spec.yaml -o out \
-t templates-with-oneof_model-override \
--additional-properties=useJakartaEe=true,serializationLibrary=jackson,interfaceOnly=true,enumPropertyNaming=original \
--global-property=models,supportingFiles=false
Generated OrderItem.kt (annotation import paths shown fully qualified as the generator emits them):
@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY, property = "kind", visible = true)
@com.fasterxml.jackson.annotation.JsonSubTypes(
com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = Cat::class, name = "CAT"),
com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = Dog::class, name = "DOG")
)
interface OrderItem
Generated Cat.kt:
data class Cat(
@JsonProperty("kind") val kind: Cat.Kind,
@JsonProperty("lives") val lives: kotlin.Int? = null
) : OrderItem
Serializing Cat(kind = Cat.Kind.CAT, lives = 9) with plain Jackson (ObjectMapper().writeValueAsString(...)):
- Actual output:
{"item":{"kind":"CAT","kind":"CAT","lives":9}}— invalid JSON with a duplicated key (and will throw withJsonGenerator.Feature.STRICT_DUPLICATE_DETECTIONenabled, or silently produce malformed/ambiguous JSON otherwise). - Expected output:
{"item":{"kind":"CAT","lives":9}}
Steps to reproduce
- Save the spec above as
spec.yaml. - Generate with
kotlin-server,library=jaxrs-spec,serializationLibrary=jackson, using a template directory that overridesoneof_model.mustacheas shown above (needed only because of the separatetypealias Anygap described below). - Compile the generated
Cat/Dog/OrderItemclasses withjackson-databindon the classpath. - Serialize a
Catinstance with a plainObjectMapper. - Observe the
kindkey appearing twice in the output JSON.
Related issues/PRs
- #21733 — [BUG] [kotlin-client][kotlin-spring] Discriminator is serialized twice (same root cause, different generators/libraries; this report covers
kotlin-server/jaxrs-spec, which wasn't included there). - kotlin-spring works around the equivalent problem with
@JsonIgnoreProperties(value = ["kind"], allowSetters = true)on top of the same@JsonTypeInfo(As.PROPERTY, ...), so a similar annotation could also fixkotlin-serverinstead of changingAs.PROPERTY. - Separately (not the subject of this report, but relevant to reproducing it):
kotlin-server's ownlibraries/jaxrs-spec/oneof_model.mustachecurrently rendersoneOfschemas astypealias X = Anywith no polymorphism/annotations at all, and theallOf-with-discriminator path in the same library referencesinterface_req_var/interface_opt_vartemplates that don't exist forjaxrs-spec, causing aTemplateNotFoundException. These look like separate, pre-existing gaps inkotlin-server/jaxrs-specpolymorphism support.
Suggest a fix
The fix is a one-token change in modules/openapi-generator/src/main/resources/kotlin-server/typeInfoAnnotation.mustache: replace As.PROPERTY with As.EXISTING_PROPERTY. As.EXISTING_PROPERTY tells Jackson to use the already-declared property for the discriminator value instead of injecting a synthetic one, which is exactly correct here because the generator always also emits that property on the subtype. No other part of the annotation (JsonSubTypes, visible, etc.) needs to change.
Diff:
[email protected](use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY, property = "{{discriminator.propertyBaseName}}", visible = {{#vendorExtensions.x-discriminator-visible-true}}true{{/vendorExtensions.x-discriminator-visible-true}}{{^vendorExtensions.x-discriminator-visible-true}}false{{/vendorExtensions.x-discriminator-visible-true}})
[email protected](use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.EXISTING_PROPERTY, property = "{{discriminator.propertyBaseName}}", visible = {{#vendorExtensions.x-discriminator-visible-true}}true{{/vendorExtensions.x-discriminator-visible-true}}{{^vendorExtensions.x-discriminator-visible-true}}false{{/vendorExtensions.x-discriminator-visible-true}})
@com.fasterxml.jackson.annotation.JsonSubTypes(
{{#discriminator.mappedModels}}
com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = {{modelName}}::class, name = "{{^vendorExtensions.x-discriminator-value}}{{mappingName}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{vendorExtensions.x-discriminator-value}}{{/vendorExtensions.x-discriminator-value}}"){{^-last}},{{/-last}}
{{/discriminator.mappedModels}}
)
Full fixed template for reference:
@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.EXISTING_PROPERTY, property = "{{discriminator.propertyBaseName}}", visible = {{#vendorExtensions.x-discriminator-visible-true}}true{{/vendorExtensions.x-discriminator-visible-true}}{{^vendorExtensions.x-discriminator-visible-true}}false{{/vendorExtensions.x-discriminator-visible-true}})
@com.fasterxml.jackson.annotation.JsonSubTypes(
{{#discriminator.mappedModels}}
com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = {{modelName}}::class, name = "{{^vendorExtensions.x-discriminator-value}}{{mappingName}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{vendorExtensions.x-discriminator-value}}{{/vendorExtensions.x-discriminator-value}}"){{^-last}},{{/-last}}
{{/discriminator.mappedModels}}
)
I verified this fix locally: generating with the original template and serializing a Cat instance via plain Jackson produces {"item":{"kind":"CAT","kind":"CAT","lives":9}}; regenerating with only As.PROPERTY → As.EXISTING_PROPERTY changed produces {"item":{"kind":"CAT","lives":9}}, with no other differences in the generated code.
PS: This bug report was partly generated with AI (Sonnet 5.5) by anonymizing a real example from production code I a not allowed to share, sorry.
- Lenguaje dominante
- Java
- Estrellas
- 26.8k
- Forks
- 7.8k
- Merge medio
- 2 d 1 h
- PR fusionados (30 d)
- 197
Preparar el entorno
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Incluye un Dockerfile o un archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de OpenAPITools/openapi-generator
-
[REQ][JAVA][resteasy] Support for Jackson 3 / useJackson3Posiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. AbiertoEnhancement: Feature
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
OpenAPITools/openapi-generator#25364 ·
Los mantenedores suelen responder en 1 día
-
[BUG][RUST] chrono date-time path/query/header params are not serialized as RFC 3339Posiblemente ocupada @teovillanueva la tomó hace 18 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
OpenAPITools/openapi-generator#25297 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
OpenAPITools/openapi-generator#25237 ·
Los mantenedores suelen responder en 1 día
-
[BUG] OpenApiEvaluator reports "default value not in enum" for numerically equal values with different scale (e.g. 1.5 and 1.50)Posiblemente ocupada @mohitduhan19 la tomó hace 23 días. AbiertoIssue: Bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
OpenAPITools/openapi-generator#25233 ·
Los mantenedores suelen responder en 1 día
-
[BUG] OpenApiEvaluator emits "Unused model" warnings even when recommendations are disabledPosiblemente ocupada @mohitduhan19 la tomó hace 23 días. AbiertoIssue: Bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
OpenAPITools/openapi-generator#25232 ·
Los mantenedores suelen responder en 1 día
Todos los issues de OpenAPITools/openapi-generator
Issues similares
-
vaadin-map
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
vaadin/flow-components#10586 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 63/100
wpilibsuite/SystemcoreTesting#446 ·
Los mantenedores suelen responder en 1 día
-
frontend quality-assurance
Dificultad 2/5 1-3 horas Aptitud para principiantes 64/100
Paul-Austin-Oswego-CSC480-HCI521/gift-app#212 ·
Los mantenedores suelen responder en 1 día
-
kuboard 在 PostgreSQL 上的迁移幂等 bugAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
eip-work/kuboard-press#700 ·
-
Mend: dependency security vulnerability
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
Los mantenedores suelen responder en 1 día