nestjs/swagger

How to make object query parameters

Closed this issue ยท 12 comments

I'm trying to figure out how to make my ApiModelProperty decorators so that Swagger would work with objects as query parameters. My case is something like this:

class PersonQueryDto {
  @ApiModelProperty({
    type: QueryFilterDto,
    isArray: true,
    example: [{ field: 'age', comparator: 'gt', value: 25 }],
  })
  readonly filters: QueryFilterDto[];
}

class QueryFilterDto {
  @ApiModelProperty({
    type: 'string',
  })
  readonly field: string; 

  @ApiModelProperty({
    type: 'string',
  })
  readonly comparator: string; 

  @ApiModelProperty({
    type: 'any',
  })
  readonly value: any; 
}

The swagger UI, however, ends up looking like this

image

And it doesn't know how to deserialise the object passed.

What's the trick?

@bostrom You should pass String instead of 'string' in type property. Also you should define your example in QueryFilterDto like this

class QueryFilterDto {
  @ApiModelProperty({
    type: String,
    example: 'age',
  })
  readonly field: string; 

  @ApiModelProperty({
    type: String,
    example: 'gt',
  })
  readonly comparator: string; 

  @ApiModelProperty({
    type: Object,
    example: 25,
  })
  readonly value: any; 
}

Thanks @nursik. So I changed the things you mentioned, but Swagger doesn't seem to be able to serialise the parameters into a query string. The resulting URL is

query?size=10&start=0&filters=%5Bobject%20Object%5D

i.e. the filters is just seen as an "[object Object]".

The problem isn't the QueryFilterDto since it contains only primitives, but the PersonQueryDto that contains a reference to a complex object.

@bostrom Actually the problem is disambiguation. How we can get query as array of objects? I think you should reconsider your query for filters parameter, because there are many different ways to pass array of objects.

Also I suggest filters query to accept array of strings rather than array of objects. So you can send like this: query?filters=age,gt,25&filters=age,lt,30 (As several web frameworks do sorting in such manner). So your QueryFilterDTO object is created from string. Transform decorator from class-transformer can help you.

@nursik Yes, having query params encoded as comma delimited strings would work. And actually that's what I'm aiming at.

The interpretation of the query string in the backend is not my problem, the problem is entering the query parameters using Swagger UI in a way that Swagger UI would allow me to input the parameters in some form fields and it would encode the parameters according to some settings as explained here https://swagger.io/docs/specification/serialization/

The problem is that I don't seem to be able to configure Swagger properly through the ApiModelProperty. I.e. how do I tell Swagger what type of data the query parameters expect and how to encode them on the URL?

The only way I could solve this is to use POST and send the query object in the body.

@bostrom Any luck with figuring it out?
I stumbled on the same issue, not only example (using default atm) is not deserialized, but also nested object is not being generated as ready inputs.

@Pietrox it's been a while but IIRC I never got this to work properly.

jmls commented

I've hit this as well - anyone got any idea on how to generate a model / class / interface that could be passed as a query parameter for swagger to visualize ?

Same problem here. I have to pass objects in query as polymorphic array.
Example from docs:

@ApiProperty({
  type: 'array',
  items: {
    oneOf: [
      { $ref: getSchemaPath(Cat) },
      { $ref: getSchemaPath(Dog) },
    ],
  },
})
pets: Pet[];

Unfortunatelly when I'm using @query() decorator in my controller, swagger-ui cannot display given endpoint.

jmls commented

this seems to work ..

@ApiProperty({ type: [String] })

nartc commented

OpenAPI 3.0 spec does not allow for nested query. If you want to annotate a query with a key of filters that is an array, use ApiQuery
image

here's the output:
image

Closing.

Highjacking this thread since it helped me figure out how to do a similar task with a simple object with full Swagger support:

Example: ?filters[name]=Marko&filters[age]=21

    ApiExtraModels(QueryFilterDto),
    ApiQuery({
      required: false,
      name: 'filters',
      style: 'deepObject',
      explode: true,
      type: 'object',
      schema: {
        $ref: getSchemaPath(QueryFilterDto),
      },
    }),

Full example here: https://gist.github.com/MarZab/c6311f83dec6401966e847c55d81a9bb

You can do something like this:

export class OrderDto {
	@IsEnum(SORT)
	@IsOptional()
	@Expose()
	@ApiPropertyOptional({enum: SORT, name: 'orderBy[createdAt]'})
	createdAt?:  @SORT;

	@IsEnum(SORT)
	@IsOptional()
	@Expose()
	@ApiPropertyOptional({enum: SORT, name: 'orderBy[updatedAt]'})
	updatedAt?: SORT;

	@IsEnum(SORT)
	@IsOptional()
	@Expose()
	@ApiPropertyOptional({enum: SORT, name: 'orderBy[name]'})
	name?: SORT;
}

export class CollectionDto  {
        // ......

	@ValidateNested()
	@IsOptional()
	@ApiPropertyOptional({type: () => OrderDto})
	@Type(() => OrderDto)
	@Expose()
	orderBy?: OrderDto;
}

example