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: [email protected]
        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: [email protected]
        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: [email protected]
        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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions