Skip to content

[Bug]: Swagger Parser generates duplicate components with sequential suffixes when using mixed reference patterns #2217

@dbr-cbk

Description

@dbr-cbk

Description

When using swagger-parser to resolve and bundle an OpenAPI specification that references external fragments, the library generates duplicate components with sequential suffixes (e.g., EmployeeInfo_1) even when there are no actual naming conflicts in the source files.

Affected Version

2.1.27

Steps to Reproduce

  1. Create a main OpenAPI specification file (main.yaml) that references schemas from an external fragment
  2. Create a fragment file (fragments.yaml) with a component used in multiple contexts:
    2.1. As intermediate references in requests (PostEmployeeRequest → EmployeeInfo)
    2.2. As direct references in responses (EmployeeInfo200 → EmployeeInfo)
    2.3. As array element references (EmployeesInfoPage.content.items → EmployeeInfo)
  3. Use swagger-parser to resolve and bundle the specification
  4. Observe that duplicate components with suffixes are generated

main.yaml

openapi: "3.0.0"
info:
  title: Test API
  version: 1.0.0
paths:
  /employees:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: 'fragments.yaml#/components/schemas/PostEmployeeRequest'
      responses:
        '201':
          $ref: 'fragments.yaml#/components/responses/EmployeeInfo201'
  /employees/id:
    put:
      requestBody:
        content:
          application/json:
            schema:
              $ref: 'fragments.yaml#/components/schemas/PutEmployeeRequest'
      responses:
        '200':
          $ref: 'fragments.yaml#/components/responses/EmployeeInfo200'
  /employees/extended:
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: 'fragments.yaml#/components/schemas/EmployeesInfoPage'

fragments.yaml

openapi: "3.0.0"
info:
  title: Fragments
  version: 1.0.0
components:
  schemas:
    EmployeeInfo:
      required:
        - email
        - employeeId
      type: object
      properties:
        employeeId:
          type: string
          description: Identifier of the employee.
          example: 123e4567-e89b-12d3-a456-426614174000_1729785600000
        email:
          type: string
          format: email
          example: string@string.string
        role:
          type: string
          enum: ["REQUESTER", "ADMIN", "DEVELOPER"]
          example: ADMIN
      description: Schema used for defining the principal information of an employee.
    
    PostEmployeeRequest:
      $ref: '#/components/schemas/EmployeeInfo'
    
    PutEmployeeRequest:
      $ref: '#/components/schemas/EmployeeInfo'
    
    EmployeesInfoPage:
      type: object
      properties:
        content:
          type: array
          items:
            $ref: '#/components/schemas/EmployeeInfo'
        totalElements:
          type: integer
          example: 100
  
  responses:
    EmployeeInfo200:
      description: OK
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/EmployeeInfo'
    
    EmployeeInfo201:
      description: Created
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/EmployeeInfo'

Result (note "EmployeeInfo_1):

openapi: 3.0.0
info:
  title: Test API
  version: 1.0.0
servers:
- url: /
paths:
  /employees:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PostEmployeeRequest'
      responses:
        "201":
          $ref: '#/components/responses/EmployeeInfo201'
  /employees/id:
    put:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PutEmployeeRequest'
      responses:
        "200":
          $ref: '#/components/responses/EmployeeInfo200'
        "201":
          $ref: '#/components/responses/EmployeeInfo201'
  /employees/extended:
    get:
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmployeesInfoPage'
components:
  schemas:
    PostEmployeeRequest:
      $ref: '#/components/schemas/EmployeeInfo'
    EmployeeInfo:
      required:
      - email
      - employeeId
      type: object
      properties:
        employeeId:
          type: string
          description: Identifier of the employee.
          example: 123e4567-e89b-12d3-a456-426614174000_1729785600000
        email:
          type: string
          description: Email of the employee.
          format: email
          example: string@string.string
        groupTags:
          $ref: '#/components/schemas/ArrayOfGroups'
        role:
          type: string
          description: "Role of the employee inside of PDH. Three possible values:\
            \ REQUESTER, ADMIN and DEVELOPER"
          example: ADMIN
          enum:
          - REQUESTER
          - ADMIN
          - DEVELOPER
      description: Schema used for defining the principal information of an employee.
    ArrayOfGroups:
      type: array
      description: Schema used for defining an array of groups
      items:
        $ref: '#/components/schemas/GroupTags'
    GroupTags:
      type: object
      properties:
        groupTag:
          type: string
          example: group-1
    EmployeeInfo_1:
      required:
      - email
      - employeeId
      type: object
      properties:
        employeeId:
          type: string
          description: Identifier of the employee.
          example: 123e4567-e89b-12d3-a456-426614174000_1729785600000
        email:
          type: string
          description: Email of the employee.
          format: email
          example: string@string.string
        groupTags:
          $ref: '#/components/schemas/ArrayOfGroups'
        role:
          type: string
          description: "Role of the employee inside of PDH. Three possible values:\
            \ REQUESTER, ADMIN and DEVELOPER"
          example: ADMIN
          enum:
          - REQUESTER
          - ADMIN
          - DEVELOPER
      description: Schema used for defining the principal information of an employee.
    PutEmployeeRequest:
      $ref: '#/components/schemas/EmployeeInfo'
    EmployeesInfoPage:
      type: object
      properties:
        content:
          type: array
          items:
            $ref: '#/components/schemas/EmployeeInfo_1'
        pageResponse:
          $ref: '#/components/schemas/PageResponse'
      description: Schema used for defining a page of employees containing also pagination
        metadata.
    PageResponse:
      type: object
      properties:
        totalElements:
          type: integer
          example: 100
        size:
          type: integer
          example: 20
  responses:
    EmployeeInfo201:
      description: Created
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/EmployeeInfo'
    EmployeeInfo200:
      description: OK
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/EmployeeInfo'

Expected Behavior

The bundled specification should contain only one EmployeeInfo component, since:

  • All references point to the same component definition
  • No naming conflicts exist in any source file
  • The component is defined only once

Workaround Applied

To prevent automatic suffix generation and gain control over component naming:
1- Create context-specific components:

Use EmployeeInfoRequest for all request schemas
Use EmployeeInfo for all response schemas
  1. Update references by context:

Result: This prevents suffix generation but requires maintaining duplicate component definitions, significantly reducing maintainability.

Impact
This behavior forces developers to choose between:

  • Accepting unpredictable component names with auto-generated suffixes
  • Duplicating component definitions to control naming (reducing maintainability)

Actual Behavior

Swagger-parser generates a bundled specification with:

  • EmployeeInfo (original component)
  • EmployeeInfo_1 (duplicate with suffix)
  • Request components (PostEmployeeRequest, PutEmployeeRequest) reference EmployeeInfo_1
  • Response components reference the original EmployeeInfo

Checklist

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions