Skip to content

Commit ae4a0db

Browse files
authored
Merge pull request #21 from JaredCE/collapse-oneOf-anyOf-with-null
Collapse oneOf anyOf with null types
2 parents f6af553 + 0e75e12 commit ae4a0db

File tree

7 files changed

+121
-26
lines changed

7 files changed

+121
-26
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Converts a standard [JSON Schema](https://json-schema.org/understanding-json-sch
88

99
As of version 0.3.0, it is now advised to run a schema through a de-referencer like: https://apitools.dev/json-schema-ref-parser/ to properly deal with `$ref`. I have removed my own poor implementation of de-referencing JSON schemas since there are libraries that can do it better than I can.
1010

11-
It should be noted, that de-referencing libraries have their own issues and might not be able to properly parse your JSON/output a schema you might expect. Due to the way OpenAPI v3.0.X Schema Object's are handled, should the referencing not be 100% correct you might face issues using this library and it's output to be used with OpenAPI 3.0.X.
11+
It should be noted, that de-referencing libraries have their own issues and might not be able to properly parse your JSON/output a schema you might expect. Due to the way OpenAPI v3.0.X Schema Object's are handled, should the referencing not be 100% correct you might face issues using this library and its output to be used with OpenAPI 3.0.X.
1212

1313
## Conversions
1414

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "json-schema-for-openapi",
3-
"version": "0.3.0",
3+
"version": "0.3.1",
44
"description": "Converts a regular JSON Schema to a compatible OpenAPI 3.0.X Schema Object",
55
"keywords": [
66
"json",

src/Convertor.js

+31-1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ class Convertor {
119119
this.removeEmptyRequired(schema)
120120
this.convertNullProperty(schema)
121121
this.convertDefaultValues(schema)
122+
this.convertOneOfAnyOfNulls(schema)
122123
this.removeInvalidFields(schema)
123124
}
124125

@@ -310,7 +311,7 @@ class Convertor {
310311
Object.assign(this.components, {schemas: {[ifSchemaRefName]: schema.if}})
311312
}
312313

313-
if (schema?.then && schema?.else) {
314+
if (schema?.then || schema?.else) {
314315
let oneOf = []
315316
if (schema.then) {
316317
oneOf.push({
@@ -414,6 +415,35 @@ class Convertor {
414415
schema.anyOf = anyOf
415416
}
416417
}
418+
419+
convertOneOfAnyOfNulls(schema) {
420+
if (schema.oneOf || schema.anyOf) {
421+
const isOneOf = Boolean(schema.oneOf)
422+
const schemaOf = schema.oneOf || schema.anyOf
423+
const hasNullType = schemaOf.some(obj => {
424+
if (obj.type === 'null')
425+
return true
426+
})
427+
428+
if (hasNullType) {
429+
schemaOf.forEach(obj => {
430+
if (obj.type !== 'null') {
431+
obj.nullable = true
432+
}
433+
})
434+
const newOf = schemaOf.filter(obj => {
435+
if (obj.type !== 'null')
436+
return obj
437+
})
438+
439+
if (isOneOf) {
440+
schema.oneOf = newOf
441+
} else {
442+
schema.anyOf = newOf
443+
}
444+
}
445+
}
446+
}
417447
}
418448

419449
module.exports = Convertor

test/schemas/ofNulls/anyOfNull.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "JSON API Schema",
4+
"description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org",
5+
"type": "object",
6+
"properties": {
7+
"payment": {
8+
"anyOf": [
9+
{
10+
"type": "null"
11+
},
12+
{
13+
"type": "string"
14+
}
15+
]
16+
}
17+
}
18+
}

test/schemas/ofNulls/oneOfNull.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "JSON API Schema",
4+
"description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org",
5+
"type": "object",
6+
"properties": {
7+
"payment": {
8+
"oneOf": [
9+
{
10+
"type": "null"
11+
},
12+
{
13+
"type": "string"
14+
}
15+
]
16+
}
17+
}
18+
}

test/src/Convertor.spec.js

+50-21
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ const camelCased = require('../schemas/camelCasedKey/camelCasedKey.json')
5050
const arrayKeyOneOf = require('../schemas/arrayKeys/arrayKeyOneOf.json')
5151
// External Schemas That I Cannot Currently Convert
5252
const listOfBannedSchemas = require('../schemas/SchemasThatCannotBeConverted/list.json')
53+
// anyOf/oneOf Nulls
54+
const oneOfNull = require('../schemas/ofNulls/oneOfNull.json')
55+
const anyOfNull = require('../schemas/ofNulls/anyOfNull.json')
5356

5457
// OpenAPI
5558
const basicOpenAPI = require('../openAPI/basic.json')
@@ -381,9 +384,9 @@ describe('Convertor', () => {
381384
const result = newConvertor.convert('basic')
382385
expect(result.schemas.basic.properties).to.have.property('street_address')
383386
expect(result.schemas.basic.properties).to.have.property('country')
384-
// expect(result.schemas.basic.properties.names.type).to.be.equal('array')
385-
// expect(result.schemas.basic.properties.names.items).to.be.an('object')
386-
// expect(result.schemas.basic.properties.names.items).to.not.be.an('array')
387+
expect(result.schemas.basic).to.have.property('oneOf')
388+
expect(result.schemas.basic.oneOf).to.be.an('array')
389+
expect(result.schemas.basic.oneOf.length).to.be.equal(2)
387390

388391
const cloned = JSON.parse(JSON.stringify(basicOpenAPI))
389392
Object.assign(cloned, {components: result})
@@ -399,9 +402,9 @@ describe('Convertor', () => {
399402
const result = newConvertor.convert('basic')
400403
expect(result.schemas.basic.properties).to.have.property('street_address')
401404
expect(result.schemas.basic.properties).to.have.property('country')
402-
// expect(result.schemas.basic.properties.names.type).to.be.equal('array')
403-
// expect(result.schemas.basic.properties.names.items).to.be.an('object')
404-
// expect(result.schemas.basic.properties.names.items).to.not.be.an('array')
405+
expect(result.schemas.basic).to.have.property('oneOf')
406+
expect(result.schemas.basic.oneOf).to.be.an('array')
407+
expect(result.schemas.basic.oneOf.length).to.be.equal(1)
405408

406409
const cloned = JSON.parse(JSON.stringify(basicOpenAPI))
407410
Object.assign(cloned, {components: result})
@@ -417,9 +420,9 @@ describe('Convertor', () => {
417420
const result = newConvertor.convert('basic')
418421
expect(result.schemas.basic.properties).to.have.property('street_address')
419422
expect(result.schemas.basic.properties).to.have.property('country')
420-
// expect(result.schemas.basic.properties.names.type).to.be.equal('array')
421-
// expect(result.schemas.basic.properties.names.items).to.be.an('object')
422-
// expect(result.schemas.basic.properties.names.items).to.not.be.an('array')
423+
expect(result.schemas.basic).to.have.property('oneOf')
424+
expect(result.schemas.basic.oneOf).to.be.an('array')
425+
expect(result.schemas.basic.oneOf.length).to.be.equal(1)
423426

424427
const cloned = JSON.parse(JSON.stringify(basicOpenAPI))
425428
Object.assign(cloned, {components: result})
@@ -435,9 +438,7 @@ describe('Convertor', () => {
435438
const result = newConvertor.convert('basic')
436439
expect(result.schemas.basic.properties).to.have.property('street_address')
437440
expect(result.schemas.basic.properties).to.have.property('country')
438-
// expect(result.schemas.basic.properties.names.type).to.be.equal('array')
439-
// expect(result.schemas.basic.properties.names.items).to.be.an('object')
440-
// expect(result.schemas.basic.properties.names.items).to.not.be.an('array')
441+
expect(result.schemas.basic).to.not.have.property('oneOf')
441442

442443
const cloned = JSON.parse(JSON.stringify(basicOpenAPI))
443444
Object.assign(cloned, {components: result})
@@ -453,9 +454,7 @@ describe('Convertor', () => {
453454
const result = newConvertor.convert('basic')
454455
expect(result.schemas.basic.properties).to.have.property('street_address')
455456
expect(result.schemas.basic.properties).to.have.property('country')
456-
// expect(result.schemas.basic.properties.names.type).to.be.equal('array')
457-
// expect(result.schemas.basic.properties.names.items).to.be.an('object')
458-
// expect(result.schemas.basic.properties.names.items).to.not.be.an('array')
457+
expect(result.schemas.basic).to.not.have.property('oneOf')
459458

460459
const cloned = JSON.parse(JSON.stringify(basicOpenAPI))
461460
Object.assign(cloned, {components: result})
@@ -471,9 +470,7 @@ describe('Convertor', () => {
471470
const result = newConvertor.convert('basic')
472471
expect(result.schemas.basic.properties).to.have.property('street_address')
473472
expect(result.schemas.basic.properties).to.have.property('country')
474-
// expect(result.schemas.basic.properties.names.type).to.be.equal('array')
475-
// expect(result.schemas.basic.properties.names.items).to.be.an('object')
476-
// expect(result.schemas.basic.properties.names.items).to.not.be.an('array')
473+
expect(result.schemas.basic).to.not.have.property('oneOf')
477474

478475
const cloned = JSON.parse(JSON.stringify(basicOpenAPI))
479476
Object.assign(cloned, {components: result})
@@ -489,9 +486,7 @@ describe('Convertor', () => {
489486
const result = newConvertor.convert('basic')
490487
expect(result.schemas.basic.properties).to.have.property('street_address')
491488
expect(result.schemas.basic.properties).to.have.property('country')
492-
// expect(result.schemas.basic.properties.names.type).to.be.equal('array')
493-
// expect(result.schemas.basic.properties.names.items).to.be.an('object')
494-
// expect(result.schemas.basic.properties.names.items).to.not.be.an('array')
489+
expect(result.schemas.basic).to.not.have.property('oneOf')
495490

496491
const cloned = JSON.parse(JSON.stringify(basicOpenAPI))
497492
Object.assign(cloned, {components: result})
@@ -604,6 +599,40 @@ describe('Convertor', () => {
604599
});
605600
});
606601

602+
describe('anyOf and oneOf with an object of type null', () => {
603+
it('should convert an anyOf with a type of null', async function() {
604+
const newConvertor = new Convertor(anyOfNull)
605+
const result = newConvertor.convert('basic')
606+
expect(result.schemas.basic.properties.payment).to.have.property('anyOf')
607+
expect(result.schemas.basic.properties.payment.anyOf).to.be.an('array')
608+
expect(result.schemas.basic.properties.payment.anyOf.length).to.be.equal(1)
609+
610+
const cloned = JSON.parse(JSON.stringify(basicOpenAPI))
611+
Object.assign(cloned, {components: result})
612+
expect(cloned).to.have.property('components')
613+
expect(cloned.components).to.have.property('schemas')
614+
expect(cloned.components.schemas).to.have.property('basic')
615+
let valid = await validator.validateInner(cloned, {})
616+
expect(valid).to.be.true
617+
});
618+
619+
it('should convert a oneOf with a type of null', async function() {
620+
const newConvertor = new Convertor(oneOfNull)
621+
const result = newConvertor.convert('basic')
622+
expect(result.schemas.basic.properties.payment).to.have.property('oneOf')
623+
expect(result.schemas.basic.properties.payment.oneOf).to.be.an('array')
624+
expect(result.schemas.basic.properties.payment.oneOf.length).to.be.equal(1)
625+
626+
const cloned = JSON.parse(JSON.stringify(basicOpenAPI))
627+
Object.assign(cloned, {components: result})
628+
expect(cloned).to.have.property('components')
629+
expect(cloned.components).to.have.property('schemas')
630+
expect(cloned.components.schemas).to.have.property('basic')
631+
let valid = await validator.validateInner(cloned, {})
632+
expect(valid).to.be.true
633+
});
634+
});
635+
607636
xdescribe('use a repo with lots of schemas to find failing ones', () => {
608637
it('should convert all schemas successfully', async function() {
609638
this.timeout(1000000);

0 commit comments

Comments
 (0)