swagger icon indicating copy to clipboard operation
swagger copied to clipboard

ApiProperty for unknown keys (dictionaries)

Open mxlpitelik opened this issue 3 years ago • 14 comments

Is there an existing issue that is already proposing this?

  • [X] I have searched the existing issues

Is your feature request related to a problem? Please describe it

If my schema is contain dictionaries (properties with unknown key) I dont know how create schema descriptions for it like here https://swagger.io/docs/specification/data-models/dictionaries/

Example:

class ColorDto {
  @ApiProperty()
  hex: string;
  
  @ApiProperty()
  rgb: string;
}

class RainbowDto {
  @ApiProperty()
  name: string;
  
  @???????
  colors: { [key: string] : ColorDto }
}

Describe the solution you'd like

class ColorDto {
  @ApiProperty()
  hex: string;
  
  @ApiProperty()
  rgb: string;
}

class RainbowDto {
  @ApiProperty()
  name: string;
  
  @ApiAdditionalProperty({
     type: ColorDto
  })
  colors: { [key: string] : ColorDto }
}

Teachability, documentation, adoption, migration strategy

No response

What is the motivation / use case for changing the behavior?

This is super often problem, and I dont know how to solve this trouble for now...

mxlpitelik avatar Jan 11 '22 19:01 mxlpitelik

+1 Any update on this matter?

amitzig avatar Mar 06 '22 14:03 amitzig

Any update ?

asherifhegazy avatar Mar 15 '22 15:03 asherifhegazy

Workaround

@Controller('api/v1/some-resource')
export class SomeResourceController {
  @ApiOkResponse({
    description: 'Some description.',
    schema: {
      type: 'object',
      example: {
        'key1': {
          name: 'string',
          date: 'string',
        },
        'key2': {
          name: 'string',
          date: 'string',
        },
      },
      additionalProperties: {
        $ref: '#/components/schemas/NameOfSomeDto',
      },
    },
  })
  public async readSomeResource() {}
}

madmxg avatar Mar 17 '22 11:03 madmxg

Also works via @ApiProperty({additionalProperties: {$ref: "#/components/schemas/NameOfSomeDto"}});

maxfriedmann avatar May 02 '22 10:05 maxfriedmann

Any updates on this? Unfortunately, it seems decorators cannot be attached to index signatures. :(

voidberg1 avatar Jun 03 '22 04:06 voidberg1

import { v4 } from 'uuid'

class SomeNestedDTO {
 @ApiProperty({ example: 'abc' })
 a: string

 @ApiProperty({ example: 322 })
 b: number
}

@ApiExtraModels(SomeNestedDTO)
class MainDTO {
  @ApiProperty({ 
    type: 'object',
    properties: { [v4()]: { $ref: '#/components/schemas/SomeNestedDTO' } }
  })
  someNestedDTOMap: Record<string, SomeNestedDTO> 
}

The result should look like:

{
  "someNestedDTOMap": {
    "d9472a82-8c2c-4f8b-a1cc-8d22f8929bd3": {
      "a": "abc",
      "b": 322
    }
  }
}

ouzkhanmete avatar Jan 25 '23 13:01 ouzkhanmete

Use Record<string, T>. If you're pre-4.0 you're probably SOL.

export class MyDto {
  @ApiProperty({ additionalProperties: { type: 'string' } });
  bag_of_fun: Record<string, string>;
}

iiian avatar Jan 27 '23 04:01 iiian

+1

It makes these properties optional and generating mock data against openapi specification is then incorrect.

deyceg avatar Mar 22 '23 12:03 deyceg

What about this solution?

import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
class ColorDto {
  @ApiProperty()
  hex: string;

  @ApiProperty()
  rgb: string;
}

@ApiExtraModels(ColorDto)
class RainbowDto {
  @ApiProperty()
  name: string;

  @ApiProperty({
    type: 'object',
    additionalProperties: { $ref: getSchemaPath(ColorDto) },
  })
  colors: Record<string, ColorDto>;
}

The result should look like:

{
  "name": "string",
  "colors": {
    "additionalProp1": {
      "hex": "string",
      "rgb": "string"
    },
    "additionalProp2": {
      "hex": "string",
      "rgb": "string"
    },
    "additionalProp3": {
      "hex": "string",
      "rgb": "string"
    }
  }
}

Kibyra avatar Mar 23 '23 09:03 Kibyra

While @Kibyra answer works for nested properties, we still can't declare class level indexed properties as swagger definition like this:

import { ApiProperty } from '@nestjs/swagger';

class ColorDto {
  // Can't apply decorator here
  [key: string]: unknown; 

  @ApiProperty()
  hex: string;

  @ApiProperty()
  rgb: string;
}

Can a class decorator could be considered allowing to add additional properties to the class ?

kervral avatar Aug 28 '23 10:08 kervral

ApiPropertyOptional could only help you make one layer, which means it could only solve Record<string, Dto>, but it can't solve Record<string, Record<string, Dto>>

Ethan199412 avatar Aug 29 '23 11:08 Ethan199412

While @Kibyra answer works for nested properties, we still can't declare class level indexed properties as swagger definition like this:

class ColorDto {
  // Can't apply decorator here
  [key: string]: unknown; 
}

I'm having the same issue.

That's a typical use case: Have an object as parameter with a unique id as key and that maps to some content.

I haven't found a proper solution for this.

Example:

  WalletAddress?: string
  Memo?: string
  DestinationTag?: string
}

export class CryptoAddresses {
  // cannot add @ApiProperty(…) → decorators are not valid here
  [id: string]: CryptoAddress
}

I can then create a controller:

  ...
  public addresses: CryptoAddresses = {}
  @ApiExtraModels(CryptoAddress)
  @Get('addresses') @ApiResponse({status: 200, schema: {type: 'object', additionalProperties: {$ref: getSchemaPath(CryptoAddress)}}}) getAddresses(): CryptoAddresses {
    return this.addresses
  }

But I can't get a schema for CryptoAddresses to be created…

For me, it's important to get a proper schema definition, because the client generates it's types from the schema file.

Any idea / solution for this?

A possible solution would be to add something to create an arbitrary schema for an arbitrary type to solve all unhandled use cases, such as:

@ApiSchema({
    type: object,
    additionalProperties: $ref: getSchemaPath(CryptoAddress)
})
class CryptoAddresses {...}

mwaeckerlin avatar Mar 25 '24 11:03 mwaeckerlin