Skip to content

[Java] [Spring] Support a custom declaration of x-setter-extra-annotation at codegen for unique arrays#23522

Open
drewlakee wants to merge 1 commit intoOpenAPITools:masterfrom
drewlakee:issue23520
Open

[Java] [Spring] Support a custom declaration of x-setter-extra-annotation at codegen for unique arrays#23522
drewlakee wants to merge 1 commit intoOpenAPITools:masterfrom
drewlakee:issue23520

Conversation

@drewlakee
Copy link
Copy Markdown

@drewlakee drewlakee commented Apr 13, 2026

fixes #123

Added isSetSetterExtensionDeclared into schema component property mappings which ignores the default behavior of Java Set generation for extra setter annotation.

Tests: Added assertion for codegen to ensure it considers a custom setter declaration for unique arrays.

PR checklist

  • Read the contribution guidelines.
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • File the PR against the correct branch: master (upcoming 7.x.0 minor release - breaking changes with fallbacks), 8.0.x (breaking changes without fallbacks)
  • If your PR solves a reported issue, reference it using GitHub's linking syntax (e.g., having "fixes #123" present in the PR description)
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

@cachescrubber (2022/02) @welshm (2022/02) @MelleD (2022/02) @atextor (2022/02) @manedev79 (2022/02) @javisst (2022/02) @borsch (2022/02) @banlevente (2022/02) @Zomzog (2022/09) @martin-mfg (2023/08)


Summary by cubic

Respect custom x-setter-extra-annotation for unique array properties (Java Set) and stop auto-adding @JsonDeserialize(as = LinkedHashSet.class) when a custom setter annotation is provided. Also propagates the extension into codegen properties and adds tests.

  • Bug Fixes
    • Detects declared setter annotation on array with uniqueItems: true and sets isSetSetterExtensionDeclared.
    • Skips default @JsonDeserialize(as = LinkedHashSet.class) when x-setter-extra-annotation is present; uses it otherwise.
    • Introduces X_SETTER_EXTRA_ANNOTATION constant and updates handling in Java codegen; adds a unit test for the custom setter case.

Written for commit e21733c. Summary will update on new commits.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 6 files

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/java/org/openapitools/codegen/DefaultCodegen.java">

<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java:4058">
P2: `isSetSetterExtensionDeclared` only checks extensions on the unaliased schema, so wrapper-level `x-setter-extra-annotation` declarations (single-item allOf or $ref wrapper) are missed even though those extensions are later merged. This leaves the flag false and allows default setter annotations to overwrite user-defined ones.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines +4058 to +4062
property.isSetSetterExtensionDeclared =
p.getUniqueItems() != null
&& p.getUniqueItems()
&& p.getExtensions() != null
&& p.getExtensions().containsKey(X_SETTER_EXTRA_ANNOTATION);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 13, 2026

Choose a reason for hiding this comment

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

P2: isSetSetterExtensionDeclared only checks extensions on the unaliased schema, so wrapper-level x-setter-extra-annotation declarations (single-item allOf or $ref wrapper) are missed even though those extensions are later merged. This leaves the flag false and allows default setter annotations to overwrite user-defined ones.

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/java/org/openapitools/codegen/DefaultCodegen.java, line 4058:

<comment>`isSetSetterExtensionDeclared` only checks extensions on the unaliased schema, so wrapper-level `x-setter-extra-annotation` declarations (single-item allOf or $ref wrapper) are missed even though those extensions are later merged. This leaves the flag false and allows default setter annotations to overwrite user-defined ones.</comment>

<file context>
@@ -4055,6 +4055,11 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo
         property.required = required;
         ModelUtils.syncValidationProperties(p, property);
         property.setFormat(p.getFormat());
+        property.isSetSetterExtensionDeclared =
+                p.getUniqueItems() != null
+                        && p.getUniqueItems()
</file context>
Suggested change
property.isSetSetterExtensionDeclared =
p.getUniqueItems() != null
&& p.getUniqueItems()
&& p.getExtensions() != null
&& p.getExtensions().containsKey(X_SETTER_EXTRA_ANNOTATION);
property.isSetSetterExtensionDeclared =
Boolean.TRUE.equals(p.getUniqueItems())
&& ((p.getExtensions() != null && p.getExtensions().containsKey(X_SETTER_EXTRA_ANNOTATION))
|| (original != null && original.getExtensions() != null
&& original.getExtensions().containsKey(X_SETTER_EXTRA_ANNOTATION)));
Fix with Cubic

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Actually, didn't get the point :/

X_SETTER_EXTRA_ANNOTATION could be inherited somehow?

        nicknames:
          type: array
          uniqueItems: true
          x-setter-extra-annotation: '@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = org.openapitools.tools.TrimmingStringSetDeserializer.class)'
          items:
            type: string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@drewlakee I suggested checking both the unaliased schema and the "original" wrapper because of how openapi-generator handles references and inheritance.

In your example, if nicknames is defined directly as an array in a model, it works fine. However, consider a case where the extension is declared on a property that references another schema (the "wrapper" case):

components:
  schemas:
    UniqueList:
      type: array
      uniqueItems: true
      items:
        type: string

    MyModel:
      properties:
        nicknames:
          $ref: '#/components/schemas/UniqueList'
          x-setter-extra-annotation: '@MyCustomAnnotation'

Inside fromProperty, the generator first identifies the property. Then, at line 4080, it calls p = unaliasSchema(p). This replaces the property definition with the resolved UniqueList schema.

The logic you added at line 4058 checks p.getExtensions(). Because p has been resolved to UniqueList, it only sees extensions defined inside UniqueList. It misses the extension defined on the property in MyModel (which is stored in the original variable).

Even though the extensions are merged into the property.vendorExtensions map later in the method, property.isSetSetterExtensionDeclared is calculated early. If that boolean remains false, AbstractJavaCodegen will proceed to add the default @JsonDeserialize(as = LinkedHashSet.class) annotation, potentially conflicting with the custom one you intended to use.

Checking original ensures that if a developer places the extension on the property usage rather than the global schema definition, it is still respected.

Cubic's product documentation | AI review settings

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Well, now I got it, but don't think it could be an appropriate use-case.

It would be better to leave it for the maintainers to think about.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant