Skip to content

Commit 58f27e3

Browse files
authored
Merge pull request #30 from JaredCE/nullable-schemas
Nullable schemas
2 parents 3b9fc9e + 402f65e commit 58f27e3

File tree

7 files changed

+183
-50
lines changed

7 files changed

+183
-50
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

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

src/Convertor.js

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ class Convertor {
186186
let defaultValue;
187187
let types = schema.type;
188188
let removeeNum = false;
189+
189190
const nullable = types.includes("null");
190191
if (nullable === true) {
191192
types = types.filter((type) => {
@@ -245,9 +246,13 @@ class Convertor {
245246
oneOf.push(newTypeObj);
246247
}
247248

248-
schema.oneOf = oneOf;
249-
if (removeeNum) delete schema.enum;
250-
delete schema.type;
249+
if (oneOf.length > 1) {
250+
schema.oneOf = oneOf;
251+
delete schema.type;
252+
if (removeeNum) delete schema.enum;
253+
} else {
254+
Object.assign(schema, oneOf[0]);
255+
}
251256
}
252257
}
253258

@@ -476,19 +481,25 @@ class Convertor {
476481
});
477482

478483
if (hasNullType) {
479-
schemaOf.forEach((obj) => {
480-
if (obj.type !== "null") {
481-
obj.nullable = true;
482-
}
483-
});
484-
const newOf = schemaOf.filter((obj) => {
485-
if (obj.type !== "null") return obj;
486-
});
484+
const nullableSchemasFiltered = schemaOf
485+
.filter((schemaContainignNull) => {
486+
if (schemaContainignNull.type !== "null") {
487+
return schemaContainignNull;
488+
}
489+
})
490+
.map((schemasNotContainingNull) => {
491+
schemasNotContainingNull.nullable = true;
492+
return schemasNotContainingNull;
493+
});
487494

488-
if (isOneOf) {
489-
schema.oneOf = newOf;
495+
if (nullableSchemasFiltered.length > 1) {
496+
if (isOneOf) schema.oneOf = nullableSchemasFiltered;
497+
else schema.anyOf = nullableSchemasFiltered;
490498
} else {
491-
schema.anyOf = newOf;
499+
if (isOneOf) delete schema.oneOf;
500+
else delete schema.anyOf;
501+
502+
Object.assign(schema, nullableSchemasFiltered[0]);
492503
}
493504
}
494505
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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+
"typedProperty": {
8+
"type": ["null", "string"]
9+
}
10+
}
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
"type": "integer"
17+
}
18+
]
19+
}
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
"type": "boolean"
17+
}
18+
]
19+
}
20+
}
21+
}

test/src/Convertor.spec.js

Lines changed: 102 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const basic = require("../schemas/basic.json");
1717
const invalidFieldOne = require("../schemas/invalidFields/invalidField.json");
1818
// null property type
1919
const nullProperty = require("../schemas/nullProperties/nullProperty.json");
20+
const nullAndTypeProperty = require("../schemas/nullProperties/nullAndType.json");
2021
// array types
2122
const arrayType = require("../schemas/arrayTypes/arrayType.json");
2223
const arrayTypeWithNull = require("../schemas/arrayTypes/arrayTypeIncludingNull.json");
@@ -55,6 +56,8 @@ const listOfBannedSchemas = require("../schemas/SchemasThatCannotBeConverted/lis
5556
// anyOf/oneOf Nulls
5657
const oneOfNull = require("../schemas/ofNulls/oneOfNull.json");
5758
const anyOfNull = require("../schemas/ofNulls/anyOfNull.json");
59+
const moreThanoneOfNull = require("../schemas/ofNulls/moreThanOneoneOf.json");
60+
const moreThananyOfNull = require("../schemas/ofNulls/moreThanOneanyOf.json");
5861
// anyOf/oneOf Nulls
5962
const allOfProperties = require("../schemas/propertiesOutsideOf/allOf.json");
6063
const oneOfProperties = require("../schemas/propertiesOutsideOf/oneOf.json");
@@ -178,24 +181,24 @@ describe("Convertor", () => {
178181
let valid = await validator.validateInner(cloned, {});
179182
expect(valid).to.be.true;
180183
});
181-
});
182184

183-
describe("arrays of types", () => {
184-
it("should convert properties that have an array of types to a oneOf", async function () {
185-
const newConvertor = new Convertor(arrayType);
185+
it(`should set types as nullable when null is provided along with a type`, async function () {
186+
const newConvertor = new Convertor(nullAndTypeProperty);
186187
const result = newConvertor.convert("basic");
188+
189+
expect(result.schemas.basic.properties.typedProperty).to.have.property(
190+
"type"
191+
);
192+
expect(result.schemas.basic.properties.typedProperty).to.have.property(
193+
"type",
194+
"string"
195+
);
196+
expect(result.schemas.basic.properties.typedProperty).to.have.property(
197+
"nullable"
198+
);
187199
expect(
188-
result.schemas.basic.properties.arrayTypeProperty
189-
).to.not.have.property("type");
190-
expect(
191-
result.schemas.basic.properties.arrayTypeProperty
192-
).to.have.property("oneOf");
193-
expect(
194-
result.schemas.basic.properties.arrayTypeProperty.oneOf
195-
).to.be.an("array");
196-
expect(
197-
result.schemas.basic.properties.arrayTypeProperty.oneOf.length
198-
).to.be.equal(2);
200+
result.schemas.basic.properties.typedProperty.nullable
201+
).to.be.equal(true);
199202

200203
const cloned = JSON.parse(JSON.stringify(basicOpenAPI));
201204
Object.assign(cloned, { components: result });
@@ -205,9 +208,11 @@ describe("Convertor", () => {
205208
let valid = await validator.validateInner(cloned, {});
206209
expect(valid).to.be.true;
207210
});
211+
});
208212

209-
it("should convert properties that have an array of types to a oneOf with null fields", async function () {
210-
const newConvertor = new Convertor(arrayTypeWithNull);
213+
describe("arrays of types", () => {
214+
it("should convert properties that have an array of types to a oneOf", async function () {
215+
const newConvertor = new Convertor(arrayType);
211216
const result = newConvertor.convert("basic");
212217
expect(
213218
result.schemas.basic.properties.arrayTypeProperty
@@ -220,13 +225,7 @@ describe("Convertor", () => {
220225
).to.be.an("array");
221226
expect(
222227
result.schemas.basic.properties.arrayTypeProperty.oneOf.length
223-
).to.be.equal(1);
224-
expect(
225-
result.schemas.basic.properties.arrayTypeProperty.oneOf[0].type
226-
).to.be.equal("string");
227-
expect(
228-
result.schemas.basic.properties.arrayTypeProperty.oneOf[0].nullable
229-
).to.be.equal(true);
228+
).to.be.equal(2);
230229

231230
const cloned = JSON.parse(JSON.stringify(basicOpenAPI));
232231
Object.assign(cloned, { components: result });
@@ -715,13 +714,18 @@ describe("Convertor", () => {
715714
it("should convert an anyOf with a type of null", async function () {
716715
const newConvertor = new Convertor(anyOfNull);
717716
const result = newConvertor.convert("basic");
718-
expect(result.schemas.basic.properties.payment).to.have.property(
717+
718+
expect(result.schemas.basic.properties.payment).to.not.have.property(
719719
"anyOf"
720720
);
721-
expect(result.schemas.basic.properties.payment.anyOf).to.be.an("array");
722-
expect(
723-
result.schemas.basic.properties.payment.anyOf.length
724-
).to.be.equal(1);
721+
expect(result.schemas.basic.properties.payment).to.have.property(
722+
"type",
723+
"string"
724+
);
725+
expect(result.schemas.basic.properties.payment).to.have.property(
726+
"nullable",
727+
true
728+
);
725729

726730
const cloned = JSON.parse(JSON.stringify(basicOpenAPI));
727731
Object.assign(cloned, { components: result });
@@ -735,13 +739,78 @@ describe("Convertor", () => {
735739
it("should convert a oneOf with a type of null", async function () {
736740
const newConvertor = new Convertor(oneOfNull);
737741
const result = newConvertor.convert("basic");
742+
expect(result.schemas.basic.properties.payment).to.not.have.property(
743+
"oneOf"
744+
);
745+
expect(result.schemas.basic.properties.payment).to.have.property(
746+
"type",
747+
"string"
748+
);
749+
expect(result.schemas.basic.properties.payment).to.have.property(
750+
"nullable",
751+
true
752+
);
753+
754+
const cloned = JSON.parse(JSON.stringify(basicOpenAPI));
755+
Object.assign(cloned, { components: result });
756+
expect(cloned).to.have.property("components");
757+
expect(cloned.components).to.have.property("schemas");
758+
expect(cloned.components.schemas).to.have.property("basic");
759+
let valid = await validator.validateInner(cloned, {});
760+
expect(valid).to.be.true;
761+
});
762+
763+
it("should convert an anyOf with a type of null and more than one non null type", async function () {
764+
const newConvertor = new Convertor(moreThananyOfNull);
765+
const result = newConvertor.convert("basic");
766+
767+
expect(result.schemas.basic.properties.payment).to.have.property(
768+
"anyOf"
769+
);
770+
expect(result.schemas.basic.properties.payment.anyOf).to.have.lengthOf(
771+
2
772+
);
773+
const stringAnyOf =
774+
result.schemas.basic.properties.payment.anyOf.filter(
775+
(schema) => schema.type === "string"
776+
);
777+
expect(stringAnyOf[0]).to.have.property("nullable", true);
778+
779+
const integerAnyOf =
780+
result.schemas.basic.properties.payment.anyOf.filter(
781+
(schema) => schema.type === "integer"
782+
);
783+
expect(integerAnyOf[0]).to.have.property("nullable", true);
784+
785+
const cloned = JSON.parse(JSON.stringify(basicOpenAPI));
786+
Object.assign(cloned, { components: result });
787+
expect(cloned).to.have.property("components");
788+
expect(cloned.components).to.have.property("schemas");
789+
expect(cloned.components.schemas).to.have.property("basic");
790+
let valid = await validator.validateInner(cloned, {});
791+
expect(valid).to.be.true;
792+
});
793+
794+
it("should convert a oneOf with a type of null and more than one non null type", async function () {
795+
const newConvertor = new Convertor(moreThanoneOfNull);
796+
const result = newConvertor.convert("basic");
738797
expect(result.schemas.basic.properties.payment).to.have.property(
739798
"oneOf"
740799
);
741-
expect(result.schemas.basic.properties.payment.oneOf).to.be.an("array");
742-
expect(
743-
result.schemas.basic.properties.payment.oneOf.length
744-
).to.be.equal(1);
800+
expect(result.schemas.basic.properties.payment.oneOf).to.have.lengthOf(
801+
2
802+
);
803+
const stringOneOf =
804+
result.schemas.basic.properties.payment.oneOf.filter(
805+
(schema) => schema.type === "string"
806+
);
807+
expect(stringOneOf[0]).to.have.property("nullable", true);
808+
809+
const booleanOneOf =
810+
result.schemas.basic.properties.payment.oneOf.filter(
811+
(schema) => schema.type === "boolean"
812+
);
813+
expect(booleanOneOf[0]).to.have.property("nullable", true);
745814

746815
const cloned = JSON.parse(JSON.stringify(basicOpenAPI));
747816
Object.assign(cloned, { components: result });

0 commit comments

Comments
 (0)