Skip to content

Commit cc614c8

Browse files
authored
Support nullable (#1)
- Add transform for `nullable: true` to include `null` as a possible type for any of the following schema types: `const`, `enum`, `type`. - Log a warning when `const` is not set to `null` but schema is marked `nullable: true` - Log a warning when `enum` does not contain `null` but schema is marked `nullable: true` - Add tests and update snapshot
1 parent 3bb5d2b commit cc614c8

File tree

6 files changed

+233
-1
lines changed

6 files changed

+233
-1
lines changed

src/normalizer.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {JSONSchemaTypeName, LinkedJSONSchema, NormalizedJSONSchema, Parent} from './types/JSONSchema'
2-
import {appendToDescription, escapeBlockComment, isSchemaLike, justName, toSafeString, traverse} from './utils'
2+
import {appendToDescription, escapeBlockComment, isSchemaLike, justName, toSafeString, traverse, warning} from './utils'
33
import {Options} from './'
44
import {DereferencedPaths} from './resolver'
55
import {isDeepStrictEqual} from 'util'
@@ -215,6 +215,35 @@ rules.set('Transform definitions to $defs', (schema, fileName) => {
215215
}
216216
})
217217

218+
rules.set('Transform nullable to null type', schema => {
219+
if (schema.nullable !== true) {
220+
return
221+
}
222+
223+
delete schema.nullable
224+
225+
if (schema.const !== undefined) {
226+
if (schema.const !== null) {
227+
warning('normalizer', 'const should be set to null when schema is nullable', schema)
228+
schema.enum = [schema.const, null]
229+
delete schema.const
230+
}
231+
} else if (schema.enum) {
232+
if (!schema.enum.includes(null)) {
233+
warning('normalizer', 'enum should include null when schema is nullable', schema)
234+
schema.enum.push(null)
235+
}
236+
} else if (schema.type) {
237+
if (Array.isArray(schema.type)) {
238+
if (!schema.type.includes('null')) {
239+
schema.type.push('null')
240+
}
241+
} else if (schema.type !== 'null') {
242+
schema.type = [schema.type, 'null']
243+
}
244+
}
245+
})
246+
218247
rules.set('Transform const to singleton enum', schema => {
219248
if (schema.const !== undefined) {
220249
schema.enum = [schema.const]

src/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,13 @@ export function error(...messages: any[]): void {
209209
console.error(getStyledTextForLogging('red')?.('error'), ...messages)
210210
}
211211

212+
export function warning(...messages: any[]): void {
213+
if (!process.env.VERBOSE) {
214+
return console.warn(messages)
215+
}
216+
console.warn(getStyledTextForLogging('yellow')?.('warning'), ...messages)
217+
}
218+
212219
type LogStyle = 'blue' | 'cyan' | 'green' | 'magenta' | 'red' | 'white' | 'yellow'
213220

214221
export function log(style: LogStyle, title: string, ...messages: unknown[]): void {

test/__snapshots__/test/test.ts.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1883,6 +1883,33 @@ Generated by [AVA](https://avajs.dev).
18831883
}␊
18841884
`
18851885

1886+
## nullable.js
1887+
1888+
> Expected output to match snapshot for e2e test: nullable.js
1889+
1890+
`/* eslint-disable */␊
1891+
/**␊
1892+
* This file was automatically generated by json-schema-to-typescript.␊
1893+
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,␊
1894+
* and run json-schema-to-typescript to regenerate this file.␊
1895+
*/␊
1896+
1897+
export interface Nullable {␊
1898+
a?: "" | null;␊
1899+
b?: null;␊
1900+
c?: "a" | "b" | null;␊
1901+
d?: "" | null;␊
1902+
e?: string | null;␊
1903+
f?: null;␊
1904+
g?: "" | null;␊
1905+
h?: null;␊
1906+
i?: "a" | "b" | null;␊
1907+
j?: "" | null;␊
1908+
k?: string | number | null;␊
1909+
l?: string | null;␊
1910+
}␊
1911+
`
1912+
18861913
## oneOf.js
18871914

18881915
> Expected output to match snapshot for e2e test: oneOf.js
-1.07 KB
Binary file not shown.

test/e2e/nullable.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
export const input = {
2+
type: 'object',
3+
properties: {
4+
a: {
5+
const: '',
6+
nullable: true,
7+
},
8+
b: {
9+
const: null,
10+
nullable: true,
11+
},
12+
c: {
13+
enum: ['a', 'b'],
14+
nullable: true,
15+
},
16+
d: {
17+
enum: ['', null],
18+
nullable: true,
19+
},
20+
e: {
21+
type: 'string',
22+
nullable: true,
23+
},
24+
f: {
25+
type: 'null',
26+
nullable: true,
27+
},
28+
g: {
29+
type: 'string',
30+
const: '',
31+
nullable: true,
32+
},
33+
h: {
34+
type: 'string',
35+
const: null,
36+
nullable: true,
37+
},
38+
i: {
39+
type: 'string',
40+
enum: ['a', 'b'],
41+
nullable: true,
42+
},
43+
j: {
44+
type: 'string',
45+
enum: ['', null],
46+
nullable: true,
47+
},
48+
k: {
49+
type: ['string', 'integer'],
50+
nullable: true,
51+
},
52+
l: {
53+
type: ['string', 'null'],
54+
nullable: true,
55+
},
56+
},
57+
additionalProperties: false,
58+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
{
2+
"name": "Nullable adds null to type",
3+
"in": {
4+
"$id": "a",
5+
"type": "object",
6+
"properties": {
7+
"a": {
8+
"const": "",
9+
"nullable": true
10+
},
11+
"b": {
12+
"const": null,
13+
"nullable": true
14+
},
15+
"c": {
16+
"enum": ["a", "b"],
17+
"nullable": true
18+
},
19+
"d": {
20+
"enum": ["", null],
21+
"nullable": true
22+
},
23+
"e": {
24+
"type": "string",
25+
"nullable": true
26+
},
27+
"f": {
28+
"type": "null",
29+
"nullable": true
30+
},
31+
"g": {
32+
"type": "string",
33+
"const": "",
34+
"nullable": true
35+
},
36+
"h": {
37+
"type": "string",
38+
"const": null,
39+
"nullable": true
40+
},
41+
"i": {
42+
"type": "string",
43+
"enum": ["a", "b"],
44+
"nullable": true
45+
},
46+
"j": {
47+
"type": "string",
48+
"enum": ["", null],
49+
"nullable": true
50+
},
51+
"k": {
52+
"type": ["string", "integer"],
53+
"nullable": true
54+
},
55+
"l": {
56+
"type": ["string", "null"],
57+
"nullable": true
58+
}
59+
},
60+
"required": [],
61+
"additionalProperties": false
62+
},
63+
"out": {
64+
"$id": "a",
65+
"type": "object",
66+
"properties": {
67+
"a": {
68+
"enum": ["", null]
69+
},
70+
"b": {
71+
"enum": [null]
72+
},
73+
"c": {
74+
"enum": ["a", "b", null]
75+
},
76+
"d": {
77+
"enum": ["", null]
78+
},
79+
"e": {
80+
"type": ["string", "null"]
81+
},
82+
"f": {
83+
"type": "null"
84+
},
85+
"g": {
86+
"type": "string",
87+
"enum": ["", null]
88+
},
89+
"h": {
90+
"type": "string",
91+
"enum": [null]
92+
},
93+
"i": {
94+
"type": "string",
95+
"enum": ["a", "b", null]
96+
},
97+
"j": {
98+
"type": "string",
99+
"enum": ["", null]
100+
},
101+
"k": {
102+
"type": ["string", "integer", "null"]
103+
},
104+
"l": {
105+
"type": ["string", "null"]
106+
}
107+
},
108+
"required": [],
109+
"additionalProperties": false
110+
}
111+
}

0 commit comments

Comments
 (0)