'Spring Boot allow Square brackets for nested objects in Request Parameters

I'm using Spring Boot 2.6.7 and org.springdoc:springdoc-openapi-ui:1.6.7 to run swagger ui with OpenApi 3 definition along with my backend.

My Setup

@GetMapping(value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
public String getHello(HelloInput input) {

    return "";
}

public class HelloInput {
    public NestedInput nested;

    // getter setter omitted
}

public class NestedInput {
    public boolean a = true;

    // getter setter omitted
}

This produces the following request when trying it out in swagger ui:

curl -X 'GET' \
  'http://localhost:8080/api/hello?nested[a]=true' \
  -H 'accept: application/json'

The Problem

However, when I execute this call I get the following Exception:

org.springframework.beans.InvalidPropertyException: Invalid property 'nested[a]' of bean class [com.proj.App.rest.explore.HelloInput]: Property referenced in indexed property path 'nested[a]' is neither an array nor a List nor a Map; returned value was [com.proj.App.rest.explore.NestedInput@68d1c7c4]

What do I need

The square braces notation causes this issue. The same call with dot notation does not cause exceptions:

    curl -X 'GET' \
  'http://localhost:8080/api/hello?nested.a.=true' \
  -H 'accept: application/json'

Is there a way to either:

A: Configure Spring Boot in a way that it accepts the square brackets notation also for object properties and not just maps, lists etc.

OR

B: Configure Swagger UI in a way that it uses the dot notation when constructing the calls?

What did I already try

I already did a lot of research, but could not find other people with this problem.

I found a feature request for Spring to support squared brackets, but it was rejected: https://github.com/spring-projects/spring-framework/issues/20052

I found basically the same question, but the answers seem to be outdated, since the RelaxedDataBinder does not seem to be a part of Spring Boot 2.6.7 anymore: Customize Spring @RequestParam Deserialization for Maps and/or Nested Objects

Other than that, no one else seems to have this problem. Am I completely misunderstanding how Spring Boot handles Request Parameters and this problem occurs because I'm breaking some convention on how to handle GET Requests with many parameters?



Solution 1:[1]

CAUTION: Hacky workaround for making swagger ui use dots instead of squared braces (probably breaks in a lot of scenarios)

Swagger Ui offers to define a requestInterceptor, that takes outbound requests and offers the user to change them.

When using Springdoc, swagger ui downloads a swagger-initializer.js for configuration of swagger ui.

enter image description here

This file is generated by an instance of a SwaggerIndexPageTransformer. The default instance already creates a requestInterceptor for CORS requests. I took that code and adapted it to change all requests with the squared braces notation to the dot notation.

For reference see AbstractSwaggerIndexTransformer#addCRSF

Workaround

First define a Configuration that offers a SwaggerIndexTransformer Bean:

@Configuration
public class OpenApiConfig {

    @Bean
    public SwaggerIndexTransformer swaggerIndexTransformer(
            SwaggerUiConfigProperties a,
            SwaggerUiOAuthProperties b,
            SwaggerUiConfigParameters c,
            SwaggerWelcomeCommon d) {

        return new CustomSwaggerIndexTransformer(a, b, c, d);
    }
}

Next implement the CustomSwaggerIndexTransformer, that now edits the swagger-initializer.js to add a requestInterceptor function, that replaces every [ with a . and removes all ].

public class CustomSwaggerIndexTransformer extends SwaggerIndexPageTransformer {

    private static final String PRESETS = "presets: [";

    ...

    @Override
    protected String defaultTransformations(InputStream inputStream) throws IOException {
        String html = super.defaultTransformations(inputStream);
        html = addDottedRequests(html);
        return html;
    }


    protected String addDottedRequests(String html) {
        String interceptorFn = """
                requestInterceptor: (request) => {
                        request.url = request.url.replaceAll("[", ".").replaceAll("]", "");
                        return request;
                    },
                """ + PRESETS;
        return html.replace(PRESETS, interceptorFn);
    }
}

Limitations

  1. This probably breaks working behavior when swagger ui uses squared braces for Maps, Arrays and Lists
  2. This does not work when CORS is enabled for springdoc, since it also defines a requestInterceptor, in that case the javascript should somehow be incorporated into the CORS requestInterceptor function.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jonathan