Skip to content

Commit aa3ba7b

Browse files
gendronbmcollina
authored andcommitted
Added support for nullable values (fixes #152) (#153)
* Added support for nullable values (fixes #152) * Corrected typos and formatting * Added compile-time checks to speed up runtime * Fixed typo, added ad-hoc functions for nullable
1 parent 5160599 commit aa3ba7b

File tree

2 files changed

+170
-19
lines changed

2 files changed

+170
-19
lines changed

index.js

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,14 @@ function build (schema, options) {
4444

4545
code += `
4646
${$asString.toString()}
47+
${$asStringNullable.toString()}
4748
${$asStringSmall.toString()}
4849
${$asNumber.toString()}
50+
${$asNumberNullable.toString()}
51+
${$asIntegerNullable.toString()}
4952
${$asNull.toString()}
5053
${$asBoolean.toString()}
54+
${$asBooleanNullable.toString()}
5155
`
5256

5357
// only handle longs if the module is used
@@ -76,16 +80,16 @@ function build (schema, options) {
7680
code = buildObject(schema, code, main, options.schema, schema)
7781
break
7882
case 'string':
79-
main = $asString.name
83+
main = schema.nullable ? $asStringNullable.name : $asString.name
8084
break
8185
case 'integer':
82-
main = $asInteger.name
86+
main = schema.nullable ? $asIntegerNullable.name : $asInteger.name
8387
break
8488
case 'number':
85-
main = $asNumber.name
89+
main = schema.nullable ? $asNumberNullable.name : $asNumber.name
8690
break
8791
case 'boolean':
88-
main = $asBoolean.name
92+
main = schema.nullable ? $asBooleanNullable.name : $asBoolean.name
8993
break
9094
case 'null':
9195
main = $asNull.name
@@ -100,7 +104,7 @@ function build (schema, options) {
100104

101105
code += `
102106
;
103-
return ${main}
107+
return ${main}
104108
`
105109

106110
if (options.uglify) {
@@ -202,6 +206,10 @@ function $asInteger (i) {
202206
}
203207
}
204208

209+
function $asIntegerNullable (i) {
210+
return i === null ? null : $asInteger(i)
211+
}
212+
205213
function $asNumber (i) {
206214
var num = Number(i)
207215
if (isNaN(num)) {
@@ -211,10 +219,18 @@ function $asNumber (i) {
211219
}
212220
}
213221

222+
function $asNumberNullable (i) {
223+
return i === null ? null : $asNumber(i)
224+
}
225+
214226
function $asBoolean (bool) {
215227
return bool && 'true' || 'false' // eslint-disable-line
216228
}
217229

230+
function $asBooleanNullable (bool) {
231+
return bool === null ? null : $asBoolean(bool)
232+
}
233+
218234
function $asString (str) {
219235
if (str instanceof Date) {
220236
return '"' + str.toISOString() + '"'
@@ -233,6 +249,10 @@ function $asString (str) {
233249
}
234250
}
235251

252+
function $asStringNullable (str) {
253+
return str === null ? null : $asString(str)
254+
}
255+
236256
// magically escape strings for json
237257
// relying on their charCodeAt
238258
// everything below 32 needs JSON.stringify()
@@ -481,6 +501,18 @@ function buildCode (schema, code, laterCode, name, externalSchema, fullSchema) {
481501
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
482502

483503
var type = schema.properties[key].type
504+
var nullable = schema.properties[key].nullable
505+
506+
if (nullable) {
507+
code += `
508+
if (obj['${key}'] === null) {
509+
${addComma}
510+
json += '${$asString(key)}:null'
511+
var rendered = true
512+
} else {
513+
`
514+
}
515+
484516
if (type === 'number') {
485517
code += `
486518
var t = Number(obj['${key}'])
@@ -549,8 +581,13 @@ function buildCode (schema, code, laterCode, name, externalSchema, fullSchema) {
549581
code += `
550582
}
551583
`
552-
})
553584

585+
if (nullable) {
586+
code += `
587+
}
588+
`
589+
}
590+
})
554591
return { code: code, laterCode: laterCode }
555592
}
556593

@@ -692,9 +729,17 @@ function buildObject (schema, code, name, externalSchema, fullSchema) {
692729
function buildArray (schema, code, name, externalSchema, fullSchema) {
693730
code += `
694731
function ${name} (obj) {
732+
`
733+
if (schema.nullable) {
734+
code += `
735+
if(obj === null) {
736+
return '${$asNull()}';
737+
}
738+
`
739+
}
740+
code += `
695741
var json = '['
696742
`
697-
698743
var laterCode = ''
699744

700745
if (schema.items['$ref']) {
@@ -798,6 +843,7 @@ function nested (laterCode, name, key, schema, externalSchema, fullSchema, subKe
798843
}
799844

800845
var type = schema.type
846+
var nullable = schema.nullable === true
801847

802848
var accessor = key.indexOf('[') === 0 ? key : `['${key}']`
803849
switch (type) {
@@ -807,24 +853,16 @@ function nested (laterCode, name, key, schema, externalSchema, fullSchema, subKe
807853
`
808854
break
809855
case 'string':
810-
code += `
811-
json += $asString(obj${accessor})
812-
`
856+
code += nullable ? `json += obj${accessor} === null ? null : $asString(obj${accessor})` : `json += $asString(obj${accessor})`
813857
break
814858
case 'integer':
815-
code += `
816-
json += $asInteger(obj${accessor})
817-
`
859+
code += nullable ? `json += obj${accessor} === null ? null : $asInteger(obj${accessor})` : `json += $asInteger(obj${accessor})`
818860
break
819861
case 'number':
820-
code += `
821-
json += $asNumber(obj${accessor})
822-
`
862+
code += nullable ? `json += obj${accessor} === null ? null : $asNumber(obj${accessor})` : `json += $asNumber(obj${accessor})`
823863
break
824864
case 'boolean':
825-
code += `
826-
json += $asBoolean(obj${accessor})
827-
`
865+
code += nullable ? `json += obj${accessor} === null ? null : $asBoolean(obj${accessor})` : `json += $asBoolean(obj${accessor})`
828866
break
829867
case 'object':
830868
funcName = (name + key + subKey).replace(/[-.\[\] ]/g, '') // eslint-disable-line

test/nullable.test.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
'use strict'
2+
3+
const test = require('tap').test
4+
5+
const build = require('..')
6+
7+
const nullable = true
8+
9+
let complexObject = {
10+
type: 'object',
11+
properties: {
12+
nullableString: { type: 'string', nullable: nullable },
13+
nullableNumber: { type: 'number', nullable: nullable },
14+
nullableInteger: { type: 'integer', nullable: nullable },
15+
nullableBoolean: { type: 'boolean', nullable: nullable },
16+
nullableNull: { type: 'null', nullable: nullable },
17+
nullableArray: {
18+
type: 'array',
19+
nullable: true,
20+
items: {}
21+
},
22+
nullableObject: { type: 'object', nullable: true },
23+
objectWithNullableProps: {
24+
type: 'object',
25+
nullable: false,
26+
additionalProperties: true,
27+
properties: {
28+
nullableString: { type: 'string', nullable: nullable },
29+
nullableNumber: { type: 'number', nullable: nullable },
30+
nullableInteger: { type: 'integer', nullable: nullable },
31+
nullableBoolean: { type: 'boolean', nullable: nullable },
32+
nullableNull: { type: 'null', nullable: nullable },
33+
nullableArray: {
34+
type: 'array',
35+
nullable: true,
36+
items: {}
37+
}
38+
}
39+
},
40+
arrayWithNullableItems: {
41+
type: 'array',
42+
nullable: true,
43+
items: { type: ['integer', 'string'], nullable: true }
44+
}
45+
}
46+
}
47+
48+
let complexData = {
49+
nullableString: null,
50+
nullableNumber: null,
51+
nullableInteger: null,
52+
nullableBoolean: null,
53+
nullableNull: null,
54+
nullableArray: null,
55+
nullableObject: null,
56+
objectWithNullableProps: {
57+
additionalProp: null,
58+
nullableString: null,
59+
nullableNumber: null,
60+
nullableInteger: null,
61+
nullableBoolean: null,
62+
nullableNull: null,
63+
nullableArray: null
64+
},
65+
arrayWithNullableItems: [ 1, 2, null ]
66+
}
67+
68+
let complexExpectedResult = {
69+
nullableString: null,
70+
nullableNumber: null,
71+
nullableInteger: null,
72+
nullableBoolean: null,
73+
nullableNull: null,
74+
nullableArray: null,
75+
nullableObject: null,
76+
objectWithNullableProps: {
77+
additionalProp: null,
78+
nullableString: null,
79+
nullableNumber: null,
80+
nullableInteger: null,
81+
nullableBoolean: null,
82+
nullableNull: null,
83+
nullableArray: null
84+
},
85+
arrayWithNullableItems: [ 1, 2, null ]
86+
}
87+
88+
let testSet = {
89+
nullableString: [ { type: 'string', nullable: nullable }, null, null ],
90+
nullableNumber: [ { type: 'number', nullable: nullable }, null, null ],
91+
nullableInteger: [ { type: 'integer', nullable: nullable }, null, null ],
92+
nullableBoolean: [ { type: 'boolean', nullable: nullable }, null, null ],
93+
nullableNull: [ { type: 'null', nullable: nullable }, null, null ],
94+
nullableArray: [ {
95+
type: 'array',
96+
nullable: true,
97+
items: {}
98+
}, null, null ],
99+
nullableObject: [ { type: 'object', nullable: true }, null, null ],
100+
complexObject: [ complexObject, complexData, complexExpectedResult ]
101+
}
102+
103+
Object.keys(testSet).forEach(key => {
104+
test(`handle nullable:true in ${key} correctly`, (t) => {
105+
t.plan(1)
106+
107+
let stringifier = build(testSet[key][0])
108+
let data = testSet[key][1]
109+
let expected = testSet[key][2]
110+
let result = stringifier(data)
111+
t.deepEqual(JSON.parse(result), expected)
112+
})
113+
})

0 commit comments

Comments
 (0)