Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Converts a standard [JSON Schema](https://json-schema.org/understanding-json-sch

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.

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.
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.

## Conversions

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "json-schema-for-openapi",
"version": "0.3.0",
"version": "0.3.1",
"description": "Converts a regular JSON Schema to a compatible OpenAPI 3.0.X Schema Object",
"keywords": [
"json",
Expand Down
32 changes: 31 additions & 1 deletion src/Convertor.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class Convertor {
this.removeEmptyRequired(schema)
this.convertNullProperty(schema)
this.convertDefaultValues(schema)
this.convertOneOfAnyOfNulls(schema)
this.removeInvalidFields(schema)
}

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

if (schema?.then && schema?.else) {
if (schema?.then || schema?.else) {
let oneOf = []
if (schema.then) {
oneOf.push({
Expand Down Expand Up @@ -414,6 +415,35 @@ class Convertor {
schema.anyOf = anyOf
}
}

convertOneOfAnyOfNulls(schema) {
if (schema.oneOf || schema.anyOf) {
const isOneOf = Boolean(schema.oneOf)
const schemaOf = schema.oneOf || schema.anyOf
const hasNullType = schemaOf.some(obj => {
if (obj.type === 'null')
return true
})

if (hasNullType) {
schemaOf.forEach(obj => {
if (obj.type !== 'null') {
obj.nullable = true
}
})
const newOf = schemaOf.filter(obj => {
if (obj.type !== 'null')
return obj
})

if (isOneOf) {
schema.oneOf = newOf
} else {
schema.anyOf = newOf
}
}
}
}
}

module.exports = Convertor
18 changes: 18 additions & 0 deletions test/schemas/ofNulls/anyOfNull.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "JSON API Schema",
"description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org",
"type": "object",
"properties": {
"payment": {
"anyOf": [
{
"type": "null"
},
{
"type": "string"
}
]
}
}
}
18 changes: 18 additions & 0 deletions test/schemas/ofNulls/oneOfNull.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "JSON API Schema",
"description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org",
"type": "object",
"properties": {
"payment": {
"oneOf": [
{
"type": "null"
},
{
"type": "string"
}
]
}
}
}
71 changes: 50 additions & 21 deletions test/src/Convertor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ const camelCased = require('../schemas/camelCasedKey/camelCasedKey.json')
const arrayKeyOneOf = require('../schemas/arrayKeys/arrayKeyOneOf.json')
// External Schemas That I Cannot Currently Convert
const listOfBannedSchemas = require('../schemas/SchemasThatCannotBeConverted/list.json')
// anyOf/oneOf Nulls
const oneOfNull = require('../schemas/ofNulls/oneOfNull.json')
const anyOfNull = require('../schemas/ofNulls/anyOfNull.json')

// OpenAPI
const basicOpenAPI = require('../openAPI/basic.json')
Expand Down Expand Up @@ -381,9 +384,9 @@ describe('Convertor', () => {
const result = newConvertor.convert('basic')
expect(result.schemas.basic.properties).to.have.property('street_address')
expect(result.schemas.basic.properties).to.have.property('country')
// expect(result.schemas.basic.properties.names.type).to.be.equal('array')
// expect(result.schemas.basic.properties.names.items).to.be.an('object')
// expect(result.schemas.basic.properties.names.items).to.not.be.an('array')
expect(result.schemas.basic).to.have.property('oneOf')
expect(result.schemas.basic.oneOf).to.be.an('array')
expect(result.schemas.basic.oneOf.length).to.be.equal(2)

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

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

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

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

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

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

const cloned = JSON.parse(JSON.stringify(basicOpenAPI))
Object.assign(cloned, {components: result})
Expand Down Expand Up @@ -604,6 +599,40 @@ describe('Convertor', () => {
});
});

describe('anyOf and oneOf with an object of type null', () => {
it('should convert an anyOf with a type of null', async function() {
const newConvertor = new Convertor(anyOfNull)
const result = newConvertor.convert('basic')
expect(result.schemas.basic.properties.payment).to.have.property('anyOf')
expect(result.schemas.basic.properties.payment.anyOf).to.be.an('array')
expect(result.schemas.basic.properties.payment.anyOf.length).to.be.equal(1)

const cloned = JSON.parse(JSON.stringify(basicOpenAPI))
Object.assign(cloned, {components: result})
expect(cloned).to.have.property('components')
expect(cloned.components).to.have.property('schemas')
expect(cloned.components.schemas).to.have.property('basic')
let valid = await validator.validateInner(cloned, {})
expect(valid).to.be.true
});

it('should convert a oneOf with a type of null', async function() {
const newConvertor = new Convertor(oneOfNull)
const result = newConvertor.convert('basic')
expect(result.schemas.basic.properties.payment).to.have.property('oneOf')
expect(result.schemas.basic.properties.payment.oneOf).to.be.an('array')
expect(result.schemas.basic.properties.payment.oneOf.length).to.be.equal(1)

const cloned = JSON.parse(JSON.stringify(basicOpenAPI))
Object.assign(cloned, {components: result})
expect(cloned).to.have.property('components')
expect(cloned.components).to.have.property('schemas')
expect(cloned.components.schemas).to.have.property('basic')
let valid = await validator.validateInner(cloned, {})
expect(valid).to.be.true
});
});

xdescribe('use a repo with lots of schemas to find failing ones', () => {
it('should convert all schemas successfully', async function() {
this.timeout(1000000);
Expand Down