[Bug]: Swagger Parser generates duplicate components with sequential suffixes when using mixed reference patterns
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
- Create a main OpenAPI specification file (main.yaml) that references schemas from an external fragment
- 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)
- Use swagger-parser to resolve and bundle the specification
- 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
- 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
- [x] I have searched the existing issues and this is not a duplicate (although similar issues were reported -e.g. #1081).
- [x] I have provided sufficient information for maintainers to reproduce the issue.
Hi @dbr-cbk I have tried to reproduce the behavior you had reported but failed. Here's the test I have written (main.yaml and fragments.yaml are copied from this issue). Can you please verify if this is the same case as yours?
@Test
public void testIssue2217ExternalReferenceResolution() {
OpenAPIV3Parser openApiParser = new OpenAPIV3Parser();
ParseOptions options = new ParseOptions();
options.setResolve(true);
options.setFlatten(true);
SwaggerParseResult parseResult = openApiParser.readLocation("issue2218/main.yaml", null, options);
OpenAPI openAPI = parseResult.getOpenAPI();
Assert.assertNotNull(openAPI, "OpenAPI should be parsed successfully");
// Assert that EmployeeInfo_1 object does not appear in the parsed spec
Assert.assertNotNull(openAPI.getComponents(), "Components should exist");
if (openAPI.getComponents().getSchemas() != null) {
Assert.assertFalse(openAPI.getComponents().getSchemas().containsKey("EmployeeInfo_1"),
"EmployeeInfo_1 should not be created during external reference resolution");
}
}`
Hi @ewaostrowska, You are right, the sample YAML files I previously provided do not generate the element. Please find attached updated versions of the files which do produce the EmployeeInfo_1 node in the resulting contract.
main.yaml
openapi: "3.0.0"
info:
title: Main test
version: 1.1.0
paths:
/int/employees/extended:
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: 'fragments.yaml#/components/schemas/EmployeesInfoPage'
/int/employees:
post:
requestBody:
content:
application/json:
schema:
$ref: 'fragments.yaml#/components/schemas/PostEmployeeRequest'
required: true
responses:
"201":
$ref: 'fragments.yaml#/components/responses/EmployeeInfo201'
/int/employees/id:
put:
requestBody:
content:
application/json:
schema:
$ref: 'fragments.yaml#/components/schemas/PutEmployeeRequest'
required: true
responses:
"200":
$ref: 'fragments.yaml#/components/responses/EmployeeInfo200'
"201":
$ref: 'fragments.yaml#/components/responses/EmployeeInfo201'
/int/employees/id/request:
post:
requestBody:
content:
application/json:
schema:
$ref: 'fragments.yaml#/components/schemas/EmployeeIdRequest'
required: true
responses:
"200":
$ref: 'fragments.yaml#/components/responses/EmployeeInfo200'
/int/employees/id/remove:
post:
requestBody:
content:
application/json:
schema:
$ref: 'fragments.yaml#/components/schemas/EmployeeIdRequest'
required: true
responses:
"204":
description: No Content
fragments.yaml
openapi: "3.0.0"
info:
version: 1.1.0
title: fr_fragments
paths: {}
components:
schemas:
CreatedByEmployeeId:
type: string
EmployeesInfoPage:
type: object
properties:
content:
type: array
items:
$ref: '#/components/schemas/EmployeeInfo'
pageResponse:
$ref: pagination.yaml#/components/schemas/PageResponse
ArrayOfGroupsInfo:
type: array
items:
$ref: '#/components/schemas/GroupInfo'
EmployeeInfo:
type: object
properties:
employeeId:
type: string
groupTags:
$ref: '#/components/schemas/ArrayOfGroups'
ArrayOfGroups:
type: array
items:
$ref: '#/components/schemas/GroupTags'
GroupTags:
type: object
properties:
groupTag:
$ref: '#/components/schemas/GroupId'
GroupId:
type: string
PostEmployeeRequest:
$ref: '#/components/schemas/EmployeeInfo'
PutEmployeeRequest:
$ref: '#/components/schemas/EmployeeInfo'
EmployeeIdRequest:
type: object
properties:
employeeId:
type: string
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):
info:
title: Main test
version: 1.1.0
servers:
- url: /
paths:
/int/employees/extended: {}
/int/employees:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PostEmployeeRequest'
required: true
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeInfo'
/int/employees/id:
put:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PutEmployeeRequest'
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeInfo'
"201":
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeInfo'
/int/employees/id/request:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeIdRequest'
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeInfo'
/int/employees/id/remove:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeIdRequest'
required: true
responses:
"204":
description: No Content
components:
schemas:
PostEmployeeRequest:
$ref: '#/components/schemas/EmployeeInfo'
EmployeeInfo:
type: object
properties:
employeeId:
type: string
groupTags:
$ref: '#/components/schemas/ArrayOfGroups'
ArrayOfGroups:
type: array
items:
$ref: '#/components/schemas/GroupTags'
GroupTags:
type: object
properties:
groupTag:
$ref: '#/components/schemas/GroupId'
GroupId:
type: string
EmployeeInfo_1:
type: object
properties:
employeeId:
type: string
groupTags:
$ref: '#/components/schemas/ArrayOfGroups'
PutEmployeeRequest:
$ref: '#/components/schemas/EmployeeInfo'
EmployeeIdRequest:
type: object
properties:
employeeId:
type: string
responses:
EmployeeInfo201:
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeInfo'
EmployeeInfo200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeInfo'
Best regards.
Should be fixed with #2105