Skip to content

Commit 8f4fdfc

Browse files
refactor: handle nullable schema in one place (#523)
1 parent feebc28 commit 8f4fdfc

File tree

2 files changed

+162
-195
lines changed

2 files changed

+162
-195
lines changed

index.js

Lines changed: 162 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -549,13 +549,6 @@ function buildObject (location) {
549549
function ${functionName} (input) {
550550
// ${schemaId + location.jsonPointer}
551551
`
552-
if (schema.nullable) {
553-
functionCode += `
554-
if (input === null) {
555-
return 'null';
556-
}
557-
`
558-
}
559552

560553
functionCode += `
561554
var obj = ${toJSON('input')}
@@ -604,14 +597,6 @@ function buildArray (location) {
604597
// ${schemaId + location.jsonPointer}
605598
`
606599

607-
if (schema.nullable) {
608-
functionCode += `
609-
if (obj === null) {
610-
return 'null';
611-
}
612-
`
613-
}
614-
615600
functionCode += `
616601
if (!Array.isArray(obj)) {
617602
throw new TypeError(\`The value '$\{obj}' does not match schema definition.\`)
@@ -732,6 +717,137 @@ function generateFuncName () {
732717
return 'anonymous' + genFuncNameCounter++
733718
}
734719

720+
function buildMultiTypeSerializer (location, input) {
721+
const schema = location.schema
722+
const types = schema.type.sort(t1 => t1 === 'null' ? -1 : 1)
723+
724+
let code = ''
725+
726+
const locationClone = clone(location)
727+
types.forEach((type, index) => {
728+
const statement = index === 0 ? 'if' : 'else if'
729+
locationClone.schema.type = type
730+
const nestedResult = buildSingleTypeSerializer(locationClone, input)
731+
switch (type) {
732+
case 'null':
733+
code += `
734+
${statement} (${input} === null)
735+
${nestedResult}
736+
`
737+
break
738+
case 'string': {
739+
code += `
740+
${statement}(
741+
typeof ${input} === "string" ||
742+
${input} === null ||
743+
${input} instanceof Date ||
744+
${input} instanceof RegExp ||
745+
(
746+
typeof ${input} === "object" &&
747+
typeof ${input}.toString === "function" &&
748+
${input}.toString !== Object.prototype.toString &&
749+
!(${input} instanceof Date)
750+
)
751+
)
752+
${nestedResult}
753+
`
754+
break
755+
}
756+
case 'array': {
757+
code += `
758+
${statement}(Array.isArray(${input}))
759+
${nestedResult}
760+
`
761+
break
762+
}
763+
case 'integer': {
764+
code += `
765+
${statement}(Number.isInteger(${input}) || ${input} === null)
766+
${nestedResult}
767+
`
768+
break
769+
}
770+
default: {
771+
code += `
772+
${statement}(typeof ${input} === "${type}" || ${input} === null)
773+
${nestedResult}
774+
`
775+
break
776+
}
777+
}
778+
})
779+
code += `
780+
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
781+
`
782+
783+
return code
784+
}
785+
786+
function buildSingleTypeSerializer (location, input) {
787+
const schema = location.schema
788+
789+
switch (schema.type) {
790+
case 'null':
791+
return 'json += \'null\''
792+
case 'string': {
793+
if (schema.format === 'date-time') {
794+
return `json += serializer.asDateTime(${input})`
795+
} else if (schema.format === 'date') {
796+
return `json += serializer.asDate(${input})`
797+
} else if (schema.format === 'time') {
798+
return `json += serializer.asTime(${input})`
799+
} else {
800+
return `json += serializer.asString(${input})`
801+
}
802+
}
803+
case 'integer':
804+
return `json += serializer.asInteger(${input})`
805+
case 'number':
806+
return `json += serializer.asNumber(${input})`
807+
case 'boolean':
808+
return `json += serializer.asBoolean(${input})`
809+
case 'object': {
810+
const funcName = buildObject(location)
811+
return `json += ${funcName}(${input})`
812+
}
813+
case 'array': {
814+
const funcName = buildArray(location)
815+
return `json += ${funcName}(${input})`
816+
}
817+
case undefined:
818+
return `json += JSON.stringify(${input})`
819+
default:
820+
throw new Error(`${schema.type} unsupported`)
821+
}
822+
}
823+
824+
function buildConstSerializer (location, input) {
825+
const schema = location.schema
826+
const type = schema.type
827+
828+
const hasNullType = Array.isArray(type) && type.includes('null')
829+
830+
let code = ''
831+
832+
if (hasNullType) {
833+
code += `
834+
if (${input} === null) {
835+
json += 'null'
836+
} else {
837+
`
838+
}
839+
840+
code += `json += '${JSON.stringify(schema.const)}'`
841+
842+
if (hasNullType) {
843+
code += `
844+
}
845+
`
846+
}
847+
848+
return code
849+
}
850+
735851
function buildValue (location, input) {
736852
let schema = location.schema
737853

@@ -759,163 +875,50 @@ function buildValue (location, input) {
759875
}
760876

761877
const type = schema.type
762-
const nullable = schema.nullable === true || (Array.isArray(type) && type.includes('null'))
763878

764879
let code = ''
765-
let funcName
766880

767-
if ('const' in schema) {
768-
if (nullable) {
881+
if (type === undefined && (schema.anyOf || schema.oneOf)) {
882+
const type = schema.anyOf ? 'anyOf' : 'oneOf'
883+
const anyOfLocation = mergeLocation(location, type)
884+
885+
for (let index = 0; index < location.schema[type].length; index++) {
886+
const optionLocation = mergeLocation(anyOfLocation, index)
887+
const schemaRef = optionLocation.schemaId + optionLocation.jsonPointer
888+
const nestedResult = buildValue(optionLocation, input)
769889
code += `
770-
json += ${input} === null ? 'null' : '${JSON.stringify(schema.const)}'
890+
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
891+
${nestedResult}
771892
`
772-
return code
773893
}
774-
code += `json += '${JSON.stringify(schema.const)}'`
894+
895+
code += `
896+
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
897+
`
775898
return code
776899
}
777900

778-
switch (type) {
779-
case 'null':
780-
code += 'json += serializer.asNull()'
781-
break
782-
case 'string': {
783-
if (schema.format === 'date-time') {
784-
funcName = nullable ? 'serializer.asDateTimeNullable.bind(serializer)' : 'serializer.asDateTime.bind(serializer)'
785-
} else if (schema.format === 'date') {
786-
funcName = nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
787-
} else if (schema.format === 'time') {
788-
funcName = nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
789-
} else {
790-
funcName = nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
791-
}
792-
code += `json += ${funcName}(${input})`
793-
break
794-
}
795-
case 'integer':
796-
funcName = nullable ? 'serializer.asIntegerNullable.bind(serializer)' : 'serializer.asInteger.bind(serializer)'
797-
code += `json += ${funcName}(${input})`
798-
break
799-
case 'number':
800-
funcName = nullable ? 'serializer.asNumberNullable.bind(serializer)' : 'serializer.asNumber.bind(serializer)'
801-
code += `json += ${funcName}(${input})`
802-
break
803-
case 'boolean':
804-
funcName = nullable ? 'serializer.asBooleanNullable.bind(serializer)' : 'serializer.asBoolean.bind(serializer)'
805-
code += `json += ${funcName}(${input})`
806-
break
807-
case 'object':
808-
funcName = buildObject(location)
809-
code += `json += ${funcName}(${input})`
810-
break
811-
case 'array':
812-
funcName = buildArray(location)
813-
code += `json += ${funcName}(${input})`
814-
break
815-
case undefined:
816-
if (schema.anyOf || schema.oneOf) {
817-
// beware: dereferenceOfRefs has side effects and changes schema.anyOf
818-
const type = schema.anyOf ? 'anyOf' : 'oneOf'
819-
const anyOfLocation = mergeLocation(location, type)
820-
821-
for (let index = 0; index < location.schema[type].length; index++) {
822-
const optionLocation = mergeLocation(anyOfLocation, index)
823-
const schemaRef = optionLocation.schemaId + optionLocation.jsonPointer
824-
const nestedResult = buildValue(optionLocation, input)
825-
code += `
826-
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
827-
${nestedResult}
828-
`
829-
}
830-
831-
code += `
832-
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
833-
`
901+
const nullable = schema.nullable === true
902+
if (nullable) {
903+
code += `
904+
if (${input} === null) {
905+
json += 'null'
834906
} else {
835-
code += `
836-
json += JSON.stringify(${input})
837-
`
838-
}
839-
break
840-
default:
841-
if (Array.isArray(type)) {
842-
let sortedTypes = type
843-
const nullable = schema.nullable === true || type.includes('null')
844-
845-
if (nullable) {
846-
sortedTypes = sortedTypes.filter(type => type !== 'null')
847-
code += `
848-
if (${input} === null) {
849-
json += null
850-
} else {`
851-
}
907+
`
908+
}
852909

853-
const locationClone = clone(location)
854-
sortedTypes.forEach((type, index) => {
855-
const statement = index === 0 ? 'if' : 'else if'
856-
locationClone.schema.type = type
857-
const nestedResult = buildValue(locationClone, input)
858-
switch (type) {
859-
case 'string': {
860-
code += `
861-
${statement}(
862-
typeof ${input} === "string" ||
863-
${input} === null ||
864-
${input} instanceof Date ||
865-
${input} instanceof RegExp ||
866-
(
867-
typeof ${input} === "object" &&
868-
typeof ${input}.toString === "function" &&
869-
${input}.toString !== Object.prototype.toString &&
870-
!(${input} instanceof Date)
871-
)
872-
)
873-
${nestedResult}
874-
`
875-
break
876-
}
877-
case 'array': {
878-
code += `
879-
${statement}(Array.isArray(${input}))
880-
${nestedResult}
881-
`
882-
break
883-
}
884-
case 'integer': {
885-
code += `
886-
${statement}(Number.isInteger(${input}) || ${input} === null)
887-
${nestedResult}
888-
`
889-
break
890-
}
891-
case 'object': {
892-
code += `
893-
${statement}(typeof ${input} === "object" || ${input} === null)
894-
${nestedResult}
895-
`
896-
break
897-
}
898-
default: {
899-
code += `
900-
${statement}(typeof ${input} === "${type}" || ${input} === null)
901-
${nestedResult}
902-
`
903-
break
904-
}
905-
}
906-
})
907-
code += `
908-
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
909-
`
910+
if (schema.const !== undefined) {
911+
code += buildConstSerializer(location, input)
912+
} else if (Array.isArray(type)) {
913+
code += buildMultiTypeSerializer(location, input)
914+
} else {
915+
code += buildSingleTypeSerializer(location, input)
916+
}
910917

911-
if (nullable) {
912-
code += `
913-
}
914-
`
915-
}
916-
} else {
917-
throw new Error(`${type} unsupported`)
918+
if (nullable) {
919+
code += `
918920
}
921+
`
919922
}
920923

921924
return code

0 commit comments

Comments
 (0)