Skip to content

[zod-openapi] Repeated schemas in openapi.json #258

@ematthewsBW

Description

@ematthewsBW

Issue

When exporting the openapi.json file, there are repeated definitions of schemas. In my real world case, it is turning what should be a ~3K lines of openapi.json into 30K lines, but I've tried to showcase a minimal example below.

Example

Controller
import { createZodDto } from '@anatine/zod-nestjs';
import { extendZodWithOpenApi } from '@anatine/zod-openapi';
import { Body, Controller, Get, Post } from '@nestjs/common';
import { ApiCreatedResponse, ApiOkResponse, ApiOperation } from '@nestjs/swagger';
import { RequestBodyValidator } from 'src/http/pipes/validator.body.request';
import { z } from 'zod';
extendZodWithOpenApi(z);

const bugResourceSchema = z.object({
  id: z.string().uuid(),
  attributes: z.object({
    name: z.string().min(1).max(250, 'name cannot be longer than 250 characters')
      .openapi({ description: 'Bug name', example: 'Bug 1' }),
  }),
});

const bugDetailSchema = z.object({
  data: bugResourceSchema,
});
class BugDetailResponse extends createZodDto(bugDetailSchema) {}

export const bugCreateSchema = z.object({
  data: bugResourceSchema.omit({ id: true }),
});
export class BugCreateRequest extends createZodDto(bugCreateSchema) {}

@Controller('bug')
export class BugController {
  constructor() {}

  @Post()
  @ApiOperation({ summary: 'Create a bug' })
  @ApiCreatedResponse({ description: 'Bug has been successfully created.', type: BugDetailResponse })
  async create(@Body(new RequestBodyValidator(bugCreateSchema)) request: BugCreateRequest): Promise<BugDetailResponse> {
    return { data: { id: '1', attributes: { name: 'Bug 1' } } };
  }

  @Get(':id')
  @ApiOperation({ summary: 'Fetch a bug' })
  @ApiCreatedResponse({ description: 'The details of a bug', type: BugDetailResponse })
  async detail(): Promise<BugDetailResponse> {
    return { data: { id: '1', attributes: { name: 'Bug 1' } } };
  }
}
openapi.json generation
  export() {
    const config = new DocumentBuilder().build();
    patchNestjsSwagger();
    const doc = {
      ...SwaggerModule.createDocument(this.app, config),
      openapi: '3.1.0',
    };
    writeFileSync('docs/openapi.json', JSON.stringify(doc));
  }
Resulting openapi.json
{
  "openapi": "3.1.0",
  "paths": {
    "/bug": {
      "post": {
        "operationId": "BugController_create",
        "summary": "Create a bug",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BugCreateRequest" } } }
        },
        "responses": {
          "201": {
            "description": "Bug has been successfully created.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BugDetailResponse" } } }
          }
        }
      },
    },
    "/bug/{id}": {
      "get": {
        "operationId": "BugController_detail",
        "summary": "Fetch a bug",
        "parameters": [],
        "responses": {
          "201": {
            "description": "The details of a bug",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BugDetailResponse" } } }
          }
        }
      }
    },
  },
  "components": {
    "schemas": {
      "BugCreateRequest": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "attributes": {
                "type": "object",
                "properties": {
                  "name": { "type": "string", "minLength": 1, "maxLength": 250, "description": "Bug name", "example": "Bug 1", "required": true }
                },
                "required": true
              }
            }
          }
        },
        "required": ["data"]
      },
      "BugDetailResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": { "type": "string", "format": "uuid", "required": true },
              "attributes": {
                "type": "object",
                "properties": {
                  "name": { "type": "string", "minLength": 1, "maxLength": 250, "description": "Bug name", "example": "Bug 1", "required": true }
                },
                "required": true
              }
            }
          }
        },
        "required": ["data"]
      },
  }
}

Expected

BugCreateRequest attributes and BugDetailResponse attributes refer to the same piece of json so that the attributes are not repeated.

Actual

BugCreateRequest attributes and BugDetailResponse redefine the attributes, resulting in duplicate JSON.

Again, small issue in this example, but i am getting much larger objects each repeated 10+ times in the doc. Is there a trick I can do to define a component and have it reused?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions