Skip to content

Nested fields in ParameterObject do not take into account annotations on parent classes #2787

Closed
@mc1arke

Description

@mc1arke

Describe the bug

For a @ParameterObject annotated value, any annotations on the fields of the target class are not taken into account when creating the flattened values for the parameters. I believe this leads to 3 types or incorrect definitions being produced:

Hidden parent fields

When a field in the @ParameterObject class is defined as hidden, or any subfields are hidden, those fields and any child fields should not included in the generated schema.

For the following endpoint definition and model

    @GetMapping("/hidden-parent")
    public void nestedParameterObjectWithHiddenParentField(@ParameterObject ParameterObjectWithHiddenField parameters) {

    }

    public record ParameterObjectWithHiddenField(
        @Schema(hidden = true) NestedParameterObject schemaHiddenNestedParameterObject,
        @Parameter(hidden = true) NestedParameterObject parameterHiddenNestedParameterObject,
        NestedParameterObject visibleNestedParameterObject
    ) {

    }

    public record NestedParameterObject(
        String parameterField) {
    }

I'd expect the schemaHiddenNestedParameterObject and parameterHiddenNestedParameterObject fields, plus any child fields, to be excluded from the generated result, e.g.

      "parameters": [
          {
            "name": "visibleNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ]

but they're currently included, e.g.

      "parameters": [
          {
            "name": "schemaHiddenNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parameterHiddenNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "visibleNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ]

Renamed parent fields

Where a field in the @ParameterObject is specifically given a name through an appropriate annotation, this name should be applied in the flattened definition, but the field name as defined in the code is currently used.

For the following endpoint definition and model

    @GetMapping("/renamed-parent")
    public void nestedParameterObjectWithRenamedParentField(@ParameterObject ParameterObjectWithRenamedField parameters) {

    }

    public record ParameterObjectWithRenamedField(
        @Schema(name = "schemaRenamed") NestedParameterObject schemaRenamedNestedParameterObject,
        @Parameter(name = "parameterRenamed") NestedParameterObject parameterRenamedNestedParameterObject,
        NestedParameterObject originalNameNestedParameterObject
    ) {

    }

    public record NestedParameterObject(
        String parameterField) {
    }

I'd expect the schemaRenamedNestedParameterObject and parameterRenamedNestedParameterObject to be given the names from the annotation in the resulting definition, e.g.

        "parameters": [
          {
            "name": "schemaRenamed.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parameterRenamed.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "originalNameNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ]

but the original field names are used, e.g.

        "parameters": [
          {
            "name": "schemaRenamedNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parameterRenamedNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "originalNameNestedParameterObject.parameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ]

Mandatory parameters in non-mandatory parent fields

Where a fields is marked as required (or resolves as required for a RequiredMode.AUTO annotation), but the parent fields is not required, that child field would not be mandatory within the request. There is a challenge here that the field would be required if any of the other fields from the parent are set, but there's no way of defining that in a flattened structure, so the definition should default to not forcing a field that's only conditionally required.

Given the following endpoint and model

    @GetMapping("/optional-parent")
    public void nestedParameterObjectWithOptionalParentField(@ParameterObject ParameterObjectWithOptionalField parameters) {

    }

    public record ParameterObjectWithOptionalField(
        @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED) NestedRequiredParameterObject schemaNotRequiredNestedParameterObject,
        @Parameter NestedRequiredParameterObject parameterNotRequiredNestedParameterObject,
        @Parameter(required = true) NestedRequiredParameterObject requiredNestedParameterObject
    ) {

    }

    public record NestedRequiredParameterObject(
        @Schema(requiredMode = Schema.RequiredMode.REQUIRED) String requiredParameterField) {
    }

I'd expect the fields makes as REQUIRED on an object that isn't set as REQUIRED to be shown as not required in the flattened definition, e.g.

        "parameters": [
          {
            "name": "schemaNotRequiredNestedParameterObject.requiredParameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parameterNotRequiredNestedParameterObject.requiredParameterField",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "requiredNestedParameterObject.requiredParameterField",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]

but the definition currently only takes into account the child element annotations to treats the fields as required, e.g.

        "parameters": [
          {
            "name": "schemaNotRequiredNestedParameterObject.requiredParameterField",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parameterNotRequiredNestedParameterObject.requiredParameterField",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "requiredNestedParameterObject.requiredParameterField",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]

Additional Information

  • The version of Spring Boot being used does not seem to have any impact. The above examples were generated using 3.4.0 and the latest SNAPSHOT of springdoc-openapi from e8de90e on the main branch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions