Expose function to get JSON schema from object
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
This is a possible made up example, but there were many other times I could use the ability to get the JSON schema from an already annotated DTO object.
Specifying the type of AnotherObjectDto | false cannot be done to my knowledge easily without writing the JSON schema manually including the contents of AnotherObjectDto :
export class SomeObjectDto {
@ApiProperty() value1!: number | string;
@ApiProperty() value2!: number | string;
@ApiPropertyOptional(/* THE UNION TYPE */) value3?: AnotherObjectDto | false;
}
class AnotherObjectDto {
@ApiProperty() value1!: number | string;
@ApiProperty() value2!: number | string;
}
Having an exported function for getting JSON schema we could do
@ApiPropertyOptional({ oneOf: [ getJsonSchema(AnotherObjectDto), {type: "boolean", enum: [ false ] } ]})
value3?: AnotherObjectDto | false;
Describe the solution you'd like
- Have a function exported which would return a schema for the given object
Workaround is importing parts from @nestjs/swagger and using:
function getJsonSchema(targetConstructor: Type<unknown>){
const factory = new SchemaObjectFactory(new ModelPropertiesAccessor(), new SwaggerTypesMapper());
const schemas: Record<string, SchemaObject> = {};
this.factory.exploreModelSchema(targetConstructor, schemas);
return schemas[targetConstructor.name];
}
Teachability, documentation, adoption, migration strategy
No response
What is the motivation / use case for changing the behavior?
Being able to use the JSON schema of the decorated object either in custom types in @ApiProperty decorator or elsewhere.
That would be very useful to mix @Body and @UploadedFile
An example implementation, in controller
// ...
@ApiUpload(MyAssetDto, 'file')
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileInterceptor('file'))
saveAsset(
@UploadedFile() file: Express.Multer.File,
@Body() data: MyAssetDto,
): Promise<void> {
// ...
}
In a upload-decorator.ts
import { Type, applyDecorators } from '@nestjs/common';
import {
ApiBody,
ApiBodyOptions,
ApiConsumes,
ApiExtraModels,
ApiOkResponse,
} from '@nestjs/swagger';
import { SchemaObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface';
import { ModelPropertiesAccessor } from '@nestjs/swagger/dist/services/model-properties-accessor';
import { SchemaObjectFactory } from '@nestjs/swagger/dist/services/schema-object-factory';
import { SwaggerTypesMapper } from '@nestjs/swagger/dist/services/swagger-types-mapper';
function getJsonSchema(targetConstructor: Type<unknown>) {
const factory = new SchemaObjectFactory(
new ModelPropertiesAccessor(),
new SwaggerTypesMapper(),
);
const schemas: Record<string, SchemaObject> = {};
factory.exploreModelSchema(targetConstructor, schemas);
return schemas[targetConstructor.name];
}
const createBodySchema = (body?: Type, fileField = 'file'): ApiBodyOptions => {
const objSchema: SchemaObject = body ? getJsonSchema(body) : undefined;
return {
schema: {
type: 'object',
properties: {
...(objSchema?.properties ? objSchema?.properties : {}),
[fileField]: {
type: 'file',
},
},
},
};
};
export function ApiUpload(body?: Type, fileField?: string) {
const extraModels = body ? [body] : [];
return applyDecorators(
ApiOkResponse(),
ApiBody(createBodySchema(body, fileField)),
ApiExtraModels(...extraModels),
);
}
Results in something like