Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swagger-ui shows null for List<MonetartyAmount>

I am using swagger on my springboot project. The problem appears for endpoint which returns the entity with list of MonetaryAmount i.e. (List<MonetaryAmount> rates)

Swagger-ui shows incorrect data for such endpoints as:

{
  "rates": [
    null
  ]
}

However I expect to see each MonetaryAmount value as pair of Double and String. Smth like this:

{
  "rates": [
   {"currency":"EUR", "rate": 12.23}
  ]
}

I tried to use directModelSubstitute but it seems it is not working for List.

Here is my model :

public class CurrencyRatesResponse implements Serializable {

private List<MonetaryAmount> rates;

public CurrencyRatesResponse() {
}

public CurrencyRatesResponse(List<MonetaryAmount> rates) {
    this.rates = rates;
}

public List<MonetaryAmount> getRates() {
    return rates;
}

public void setRates(List<MonetaryAmount> rates) {
    this.rates = rates;
}
}

And here is an example of my endpoint

@RequestMapping(path = "/public/rates", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public CurrencyRatesResponse getRates() {...}

Generated swagger JSON:

 "/public/rates":{
         "get":{
            "tags":[
               "rate-endpoint"
            ],
            "summary":"getRates",
            "operationId":"getRatesUsingGET",
            "produces":[
               "application/json;charset=UTF-8"
            ],
            "parameters":[
               {
                 ....
               }
            ],
            "responses":{
               "200":{
                  "description":"OK",
                  "schema":{
                     "$ref":"#/definitions/CurrencyRatesResponse"
                  }
               },
               "401":{
                  "description":"Unauthorized"
               },
               "403":{
                  "description":"Forbidden"
               },
               "404":{
                  "description":"Not Found"
               }
            }
         }
      }
    }  ....

   "CurrencyRatesResponse":{
         "type":"object",
         "properties":{
            "rates":{
               "type":"array",
               "items":{
                  "$ref":"#/definitions/MonetaryAmount"
               }
            }
         },
         "title":"CurrencyRatesResponse"
      },
like image 482
smaiakov Avatar asked May 01 '18 07:05

smaiakov


1 Answers

There are several steps to achieve this.

First of all, we need proper objectMapper soMonetaryAmount gets mapped to proper object with two fields currency and rate. To achieve this we need to add custom module for the springfox's objectMapper.

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.zalando.jackson.datatype.money.MoneyModule;
import springfox.documentation.schema.configuration.ObjectMapperConfigured;

@Component
public class SwaggerJacksonModule implements ApplicationListener<ObjectMapperConfigured> {

    @Override
    public void onApplicationEvent(ObjectMapperConfigured event) {
        event.getObjectMapper().registerModule(new MoneyModule());
    }
}

Explanation: We add a listener to the springfox's ObjectMapperConfigured event and whenever the event is triggered we simply get the mapper and register our module. I use Zalando's Jackson Datatype Money library so that I don't write my own.

Next step would be to work on actual Swagger representation. Let's create our own representation object MonetaryWrapper which we gonna use it to substitute MonetaryAmount.

public class MonetaryWrapper {

    private BigDecimal amount;

    private String currency;

  // getters and setters  (getters are important)
}

In order to get Single object representations right it's enough to add .directModelSubstitute(MonetaryAmount.class, MonetaryWrapper.class) to the Docket object.

BUT It's not that simple for collections. e.g List in this case. We need add Special AlternateTypeRule object into Docket object in order for lists show properly.

More details on this topic in springfox's documentation

Something like this:

TypeResolver resolver = new TypeResolver();
AlternateTypeRule monetaryAmountListRule =
        newRule(resolver.resolve(List.class,  MonetaryAmount.class),
                resolver.resolve(List.class, MonetaryWrapper.class))

And finally our Docket bean would look something like this:

@Bean
public Docket productApi() {
    TypeResolver resolver = new TypeResolver();
    AlternateTypeRule monetaryAmountListRule =
            newRule(resolver.resolve(List.class,  MonetaryAmount.class),
                    resolver.resolve(List.class, MonetaryWrapper.class));


    return new Docket(DocumentationType.SWAGGER_2)
            .alternateTypeRules(monetaryAmountListRule)
            .directModelSubstitute(MonetaryAmount.class, MonetaryWrapper.class)
            .select().apis(RequestHandlerSelectors.basePackage("com.swagger.test"))
            .paths(PathSelectors.regex("/public.*")).build();
}

And some demo screenshots here:

Demo when executing request:

DEMO endpoint result

Demo just for object description:

Demo representation result

like image 103
Amiko Avatar answered Jan 21 '23 17:01

Amiko