[Java][Spring] Use discriminator value for @JsonTypeName instead of schema name (#23997)#24003
Conversation
…chema name (OpenAPITools#23997) For the java and spring generators, child models honor an explicit x-discriminator-value vendor extension when rendering the Jackson @JsonTypeName annotation, falling back to the schema/model name when no discriminator value is available. Previously the schema/model name was always used, which broke Jackson polymorphic serialization whenever the discriminator value differed from the model name (e.g. when modelNameSuffix is used or an explicit mapping is declared). Mirrors the prior jaxrs-spec fix (OpenAPITools#23509). The template change is a no-op when no x-discriminator-value is present, so existing samples are unchanged.
There was a problem hiding this comment.
1 issue found across 12 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
||
| @Test | ||
| public void testDiscriminatorValueUsedInJsonTypeName_issue23997() throws IOException { | ||
| File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); |
There was a problem hiding this comment.
Shorter version:
Map<String, File> files = generateFromContract(
"src/test/resources/3_0/issue_23997_discriminator_jsontypename.yaml", SPRING_BOOT,
Map.of(USE_SPRING_BOOT4, true, MODEL_NAME_SUFFIX, "ProviderDTO", INTERFACE_ONLY, "true"));
There was a problem hiding this comment.
Done in 94f285f — switched the test to generateFromContract(...) with your exact Map.of(USE_SPRING_BOOT4, true, MODEL_NAME_SUFFIX, "ProviderDTO", INTERFACE_ONLY, "true"). Verified it still passes (@JsonTypeName("USER") / "COMPONENT" on the suffixed child models). Thanks for the cleanup!
|
@seonwooj0810 A better fix would be to remove the generation of the The field In short in DefaultCodegen.java line 577: |
…y test Wrap the user/spec-provided x-discriminator-value with a new escapeJava mustache lambda before rendering it into the @JsonTypeName Java string literal, so a discriminator value containing quotes, backslashes or control characters can no longer produce uncompilable generated code. The lambda is a no-op for normal identifier-like values, so existing samples are unchanged. Also simplify the Spring regression test to use the generateFromContract helper, per review feedback.
There was a problem hiding this comment.
2 issues found across 13 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache:25">
P1: Cross-file consistency: `lambda.escapeJava` was added to the child `@JsonTypeName` but the parent `@JsonSubTypes.Type` in `typeInfoAnnotation` partial does not escape the same discriminator value, causing a Jackson type-id mismatch for special-character discriminator values.</violation>
</file>
<file name="modules/openapi-generator/src/main/resources/Java/libraries/feign/pojo.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/Java/libraries/feign/pojo.mustache:19">
P2: @JsonTypeName escaping is incomplete: the x-discriminator-value branch is escaped but the fallback {{name}} branch remains unescaped, leaving the Java string literal unprotected for edge-case schema names.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| {{#isClassnameSanitized}} | ||
| {{^hasDiscriminatorWithNonEmptyMapping}} | ||
| @JsonTypeName("{{name}}") | ||
| @JsonTypeName("{{#vendorExtensions.x-discriminator-value}}{{#lambda.escapeJava}}{{{vendorExtensions.x-discriminator-value}}}{{/lambda.escapeJava}}{{/vendorExtensions.x-discriminator-value}}{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}") |
There was a problem hiding this comment.
P1: Cross-file consistency: lambda.escapeJava was added to the child @JsonTypeName but the parent @JsonSubTypes.Type in typeInfoAnnotation partial does not escape the same discriminator value, causing a Jackson type-id mismatch for special-character discriminator values.
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/JavaSpring/pojo.mustache, line 25:
<comment>Cross-file consistency: `lambda.escapeJava` was added to the child `@JsonTypeName` but the parent `@JsonSubTypes.Type` in `typeInfoAnnotation` partial does not escape the same discriminator value, causing a Jackson type-id mismatch for special-character discriminator values.</comment>
<file context>
@@ -22,7 +22,7 @@
{{#isClassnameSanitized}}
{{^hasDiscriminatorWithNonEmptyMapping}}
-@JsonTypeName("{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}")
+@JsonTypeName("{{#vendorExtensions.x-discriminator-value}}{{#lambda.escapeJava}}{{{vendorExtensions.x-discriminator-value}}}{{/lambda.escapeJava}}{{/vendorExtensions.x-discriminator-value}}{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}")
{{/hasDiscriminatorWithNonEmptyMapping}}
{{/isClassnameSanitized}}
</file context>
| {{#isClassnameSanitized}} | ||
| {{^hasDiscriminatorWithNonEmptyMapping}} | ||
| @JsonTypeName("{{name}}") | ||
| @JsonTypeName("{{#vendorExtensions.x-discriminator-value}}{{#lambda.escapeJava}}{{{vendorExtensions.x-discriminator-value}}}{{/lambda.escapeJava}}{{/vendorExtensions.x-discriminator-value}}{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}") |
There was a problem hiding this comment.
P2: @JsonTypeName escaping is incomplete: the x-discriminator-value branch is escaped but the fallback {{name}} branch remains unescaped, leaving the Java string literal unprotected for edge-case schema names.
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/Java/libraries/feign/pojo.mustache, line 19:
<comment>@JsonTypeName escaping is incomplete: the x-discriminator-value branch is escaped but the fallback {{name}} branch remains unescaped, leaving the Java string literal unprotected for edge-case schema names.</comment>
<file context>
@@ -16,7 +16,7 @@
{{#isClassnameSanitized}}
{{^hasDiscriminatorWithNonEmptyMapping}}
-@JsonTypeName("{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}")
+@JsonTypeName("{{#vendorExtensions.x-discriminator-value}}{{#lambda.escapeJava}}{{{vendorExtensions.x-discriminator-value}}}{{/lambda.escapeJava}}{{/vendorExtensions.x-discriminator-value}}{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}")
{{/hasDiscriminatorWithNonEmptyMapping}}
{{/isClassnameSanitized}}
</file context>
| @JsonTypeName("{{#vendorExtensions.x-discriminator-value}}{{#lambda.escapeJava}}{{{vendorExtensions.x-discriminator-value}}}{{/lambda.escapeJava}}{{/vendorExtensions.x-discriminator-value}}{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}") | |
| @JsonTypeName("{{#vendorExtensions.x-discriminator-value}}{{#lambda.escapeJava}}{{{vendorExtensions.x-discriminator-value}}}{{/lambda.escapeJava}}{{/vendorExtensions.x-discriminator-value}}{{^vendorExtensions.x-discriminator-value}}{{#lambda.escapeJava}}{{name}}{{/lambda.escapeJava}}{{/vendorExtensions.x-discriminator-value}}") |
|
Thanks @jpfinne — I agree your approach is the cleaner fix. For a child that's part of a non-empty discriminator mapping, the parent interface already declares the type id via I'm happy to pivot this PR to that approach right away — implement the Would you like me to take it here, or would you rather drive it yourself? Happy either way — just let me know so we don't duplicate the work. (In the meantime I applied your test-simplification suggestion in 94f285f. I'll hold the escaping changes from that commit since they become moot under this approach — I'll drop them as part of the pivot if you'd like me to proceed.) |
|
@seonwooj0810 I had already started implementing a fix. Here the result PR: #24024 Can you test it? |
Fixes #23997
Problem
For the
javaandspringgenerators, generated child models of a discriminator can receive a Jackson@JsonTypeNameannotation using the schema/model name instead of the discriminator value declared in the OpenAPI contract:This breaks Jackson polymorphic serialization whenever the discriminator value differs from the schema name — most visibly when
modelNameSuffix/modelNamePrefixis used, or when an explicitx-discriminator-valueis declared on the child schema. The parent's@JsonSubTypesalready uses the correct value, so serialization and deserialization disagree.Fix
Render
@JsonTypeNamefromvendorExtensions.x-discriminator-valuewhen present, falling back to the schema/model name otherwise — exactly the approach suggested in the issue and already used for thejaxrs-specgenerator (#23509):@JsonTypeName("{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}")Applied to the base
Java/JavaSpringpojo.mustacheand the affected Java client libraries (restclient, resttemplate, webclient, feign, jersey2, jersey3, microprofile) so the behavior is consistent across the Java/Spring generators.The change is a no-op when no
x-discriminator-valueis present (the conditional renders exactly{{name}}as before), so no existing samples change.Test evidence
Added two focused tests reproducing the issue's spec (child schemas extend a non-discriminator base and carry an explicit
x-discriminator-value, generated with a model-name suffix):SpringCodegenTest#testDiscriminatorValueUsedInJsonTypeName_issue23997JavaClientCodegenTest#testDiscriminatorValueUsedInJsonTypeName_issue23997Both assert child models emit
@JsonTypeName("USER")/@JsonTypeName("COMPONENT")rather than the suffixed schema name. Both pass:Regenerating an existing discriminator-bearing sample (
bin/configs/java-restclient.yaml, petstore) produced zero diff, confirming the template change does not affect output whenx-discriminator-valueis absent.Verification done
gh pr listsearch), no linked PRs/branches on the issue.mustachetemplates + Java tests, not docsmaster(templates emitted@JsonTypeName("{{name}}")); reproduced via the added testsJsonTypeNameGeneration WithallOfandoneOfCombination to Match Key Instead of Schema Name #23509 deliberately did not cover java/springcc @bbdouglas @sreeshas @jfiala @lukoyanov @cbornet @jimschubert (Java technical committee) and @cachescrubber @welshm @MelleD @atextor @manedev79 @javisst @borsch @banlangzhi @sahilgarg17 (Spring technical committee)
Summary by cubic
Use the discriminator value for Jackson
@JsonTypeNamein Java and Spring generators, and escape it to prevent invalid Java string literals. Fixes mismatched polymorphic serialization and avoids compile errors when values include quotes or backslashes. Fixes #23997.@JsonTypeNamefromvendorExtensions.x-discriminator-value, otherwise{{name}}; wrap withlambda.escapeJavato escape quotes, backslashes, and control chars.EscapeJavaLambda(registered asescapeJava) and applied toJava/JavaSpringpojo.mustacheand Java client libs:restclient,resttemplate,webclient,feign,jersey2,jersey3,microprofile.@JsonTypeName("USER" | "COMPONENT")is emitted and unit tests for escaping; existing samples show zero diff.Written for commit 94f285f. Summary will update on new commits.