Skip to content

Commit

Permalink
feat: enableToJSON (#525)
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-tymoshenko authored Sep 8, 2022
1 parent 07f07c8 commit 320747e
Show file tree
Hide file tree
Showing 3 changed files with 1,179 additions and 45 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ const stringify = fastJson(mySchema, {
- `rounding`: setup how the `integer` types will be rounded when not integers. [More details](#integer)
- `largeArrayMechanism`: set the mechanism that should be used to handle large
(by default `20000` or more items) arrays. [More details](#largearrays)
- `enableToJSON`: enable the `toJSON` method check for the generated function.


<a name="api"></a>
Expand Down Expand Up @@ -669,7 +670,7 @@ const debugCompiled = fastJson({

console.log(debugCompiled) // it is a object contain code, ajv instance
const rawString = debugCompiled.code // it is the generated code
console.log(rawString)
console.log(rawString)

const stringify = fastJson.restore(debugCompiled) // use the generated string to get back the `stringify` function
console.log(stringify({ firstName: 'Foo', surname: 'bar' })) // '{"firstName":"Foo"}'
Expand Down
95 changes: 51 additions & 44 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ function resolveRef (location, ref) {
const arrayItemsReferenceSerializersMap = new Map()
const objectReferenceSerializersMap = new Map()

let enableToJSON = null
let rootSchemaId = null
let refResolver = null
let validator = null
Expand All @@ -88,6 +89,7 @@ function build (schema, options) {
refResolver = new RefResolver()
validator = new Validator(options.ajv)

enableToJSON = options.enableToJSON || false
rootSchemaId = schema.$id || randomUUID()

isValidSchema(schema)
Expand Down Expand Up @@ -527,13 +529,6 @@ function addIfThenElse (location) {
return code
}

function toJSON (variableName) {
return `(${variableName} && typeof ${variableName}.toJSON === 'function')
? ${variableName}.toJSON()
: ${variableName}
`
}

function buildObject (location) {
const schema = location.schema

Expand All @@ -551,7 +546,7 @@ function buildObject (location) {
`

functionCode += `
var obj = ${toJSON('input')}
var obj = input
var json = '{'
var addComma = false
`
Expand Down Expand Up @@ -675,7 +670,7 @@ function generateFuncName () {
return 'anonymous' + genFuncNameCounter++
}

function buildMultiTypeSerializer (location, input) {
function buildMultiTypeSerializer (location) {
const schema = location.schema
const types = schema.type.sort(t1 => t1 === 'null' ? -1 : 1)

Expand All @@ -685,26 +680,26 @@ function buildMultiTypeSerializer (location, input) {
types.forEach((type, index) => {
const statement = index === 0 ? 'if' : 'else if'
locationClone.schema.type = type
const nestedResult = buildSingleTypeSerializer(locationClone, input)
const nestedResult = buildSingleTypeSerializer(locationClone)
switch (type) {
case 'null':
code += `
${statement} (${input} === null)
${statement} (input === null)
${nestedResult}
`
break
case 'string': {
code += `
${statement}(
typeof ${input} === "string" ||
${input} === null ||
${input} instanceof Date ||
${input} instanceof RegExp ||
typeof input === "string" ||
input === null ||
input instanceof Date ||
input instanceof RegExp ||
(
typeof ${input} === "object" &&
typeof ${input}.toString === "function" &&
${input}.toString !== Object.prototype.toString &&
!(${input} instanceof Date)
typeof input === "object" &&
typeof input.toString === "function" &&
input.toString !== Object.prototype.toString &&
!(input instanceof Date)
)
)
${nestedResult}
Expand All @@ -713,73 +708,73 @@ function buildMultiTypeSerializer (location, input) {
}
case 'array': {
code += `
${statement}(Array.isArray(${input}))
${statement}(Array.isArray(input))
${nestedResult}
`
break
}
case 'integer': {
code += `
${statement}(Number.isInteger(${input}) || ${input} === null)
${statement}(Number.isInteger(input) || input === null)
${nestedResult}
`
break
}
default: {
code += `
${statement}(typeof ${input} === "${type}" || ${input} === null)
${statement}(typeof input === "${type}" || input === null)
${nestedResult}
`
break
}
}
})
code += `
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
else throw new Error(\`The value $\{JSON.stringify(input)} does not match schema definition.\`)
`

return code
}

function buildSingleTypeSerializer (location, input) {
function buildSingleTypeSerializer (location) {
const schema = location.schema

switch (schema.type) {
case 'null':
return 'json += \'null\''
case 'string': {
if (schema.format === 'date-time') {
return `json += serializer.asDateTime(${input})`
return 'json += serializer.asDateTime(input)'
} else if (schema.format === 'date') {
return `json += serializer.asDate(${input})`
return 'json += serializer.asDate(input)'
} else if (schema.format === 'time') {
return `json += serializer.asTime(${input})`
return 'json += serializer.asTime(input)'
} else {
return `json += serializer.asString(${input})`
return 'json += serializer.asString(input)'
}
}
case 'integer':
return `json += serializer.asInteger(${input})`
return 'json += serializer.asInteger(input)'
case 'number':
return `json += serializer.asNumber(${input})`
return 'json += serializer.asNumber(input)'
case 'boolean':
return `json += serializer.asBoolean(${input})`
return 'json += serializer.asBoolean(input)'
case 'object': {
const funcName = buildObject(location)
return `json += ${funcName}(${input})`
return `json += ${funcName}(input)`
}
case 'array': {
const funcName = buildArray(location)
return `json += ${funcName}(${input})`
return `json += ${funcName}(input)`
}
case undefined:
return `json += JSON.stringify(${input})`
return 'json += JSON.stringify(input)'
default:
throw new Error(`${schema.type} unsupported`)
}
}

function buildConstSerializer (location, input) {
function buildConstSerializer (location) {
const schema = location.schema
const type = schema.type

Expand All @@ -789,7 +784,7 @@ function buildConstSerializer (location, input) {

if (hasNullType) {
code += `
if (${input} === null) {
if (input === null) {
json += 'null'
} else {
`
Expand All @@ -809,8 +804,13 @@ function buildConstSerializer (location, input) {
function buildValue (location, input) {
let schema = location.schema

let code = `
var input = ${input}
`

if (typeof schema === 'boolean') {
return `json += JSON.stringify(${input})`
code += 'json += JSON.stringify(input)'
return code
}

if (schema.$ref) {
Expand All @@ -834,7 +834,13 @@ function buildValue (location, input) {

const type = schema.type

let code = ''
if (enableToJSON === true) {
code += `
if (input && typeof input.toJSON === 'function' && !(input instanceof Date)) {
input = input.toJSON()
}
`
}

if (type === undefined && (schema.anyOf || schema.oneOf)) {
const type = schema.anyOf ? 'anyOf' : 'oneOf'
Expand All @@ -845,32 +851,33 @@ function buildValue (location, input) {
const schemaRef = optionLocation.schemaId + optionLocation.jsonPointer
const nestedResult = buildValue(optionLocation, input)
code += `
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", input)) {
${nestedResult}
}
`
}

code += `
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
else throw new Error(\`The value $\{JSON.stringify(input)} does not match schema definition.\`)
`
return code
}

const nullable = schema.nullable === true
if (nullable) {
code += `
if (${input} === null) {
if (input === null) {
json += 'null'
} else {
`
}

if (schema.const !== undefined) {
code += buildConstSerializer(location, input)
code += buildConstSerializer(location)
} else if (Array.isArray(type)) {
code += buildMultiTypeSerializer(location, input)
code += buildMultiTypeSerializer(location)
} else {
code += buildSingleTypeSerializer(location, input)
code += buildSingleTypeSerializer(location)
}

if (nullable) {
Expand Down
Loading

0 comments on commit 320747e

Please sign in to comment.