Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose function to get JSON schema from object #2306

Open
1 task done
SmallhillCZ opened this issue Feb 23, 2023 · 1 comment
Open
1 task done

Expose function to get JSON schema from object #2306

SmallhillCZ opened this issue Feb 23, 2023 · 1 comment
Labels

Comments

@SmallhillCZ
Copy link

Is there an existing issue that is already proposing this?

  • 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.

@muka
Copy link

muka commented Apr 2, 2024

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

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants