Socket
Socket
Sign inDemoInstall

fast-json-stringify

Package Overview
Dependencies
11
Maintainers
3
Versions
158
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.2.0 to 4.0.0

benchmark/bench-cmp-branch.js

3

example.js

@@ -1,2 +0,1 @@

const moment = require('moment')
const fastJson = require('.')

@@ -70,3 +69,2 @@ const stringify = fastJson({

now: new Date(),
birthdate: moment(),
reg: /"([^"]|\\")*"/,

@@ -76,3 +74,2 @@ foo: 'hello',

test: 42,
date: moment(),
strtest: '23',

@@ -79,0 +76,0 @@ arr: [{ str: 'stark' }, { str: 'lannister' }],

@@ -59,2 +59,169 @@ 'use strict'

class Serializer {
constructor (options = {}) {
switch (options.rounding) {
case 'floor':
this.parseInteger = Math.floor
break
case 'ceil':
this.parseInteger = Math.ceil
break
case 'round':
this.parseInteger = Math.round
break
default:
this.parseInteger = Math.trunc
break
}
}
asAny (i) {
return JSON.stringify(i)
}
asNull () {
return 'null'
}
asInteger (i) {
if (typeof i === 'bigint') {
return i.toString()
} else if (Number.isInteger(i)) {
return '' + i
} else {
/* eslint no-undef: "off" */
const integer = this.parseInteger(i)
if (Number.isNaN(integer)) {
throw new Error(`The value "${i}" cannot be converted to an integer.`)
} else {
return '' + integer
}
}
}
asIntegerNullable (i) {
return i === null ? 'null' : this.asInteger(i)
}
asNumber (i) {
const num = Number(i)
if (Number.isNaN(num)) {
throw new Error(`The value "${i}" cannot be converted to a number.`)
} else {
return '' + num
}
}
asNumberNullable (i) {
return i === null ? 'null' : this.asNumber(i)
}
asBoolean (bool) {
return bool && 'true' || 'false' // eslint-disable-line
}
asBooleanNullable (bool) {
return bool === null ? 'null' : this.asBoolean(bool)
}
asDatetime (date, skipQuotes) {
const quotes = skipQuotes === true ? '' : '"'
if (date instanceof Date) {
return quotes + date.toISOString() + quotes
}
return this.asString(date, skipQuotes)
}
asDatetimeNullable (date, skipQuotes) {
return date === null ? 'null' : this.asDatetime(date, skipQuotes)
}
asDate (date, skipQuotes) {
const quotes = skipQuotes === true ? '' : '"'
if (date instanceof Date) {
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + quotes
}
return this.asString(date, skipQuotes)
}
asDateNullable (date, skipQuotes) {
return date === null ? 'null' : this.asDate(date, skipQuotes)
}
asTime (date, skipQuotes) {
const quotes = skipQuotes === true ? '' : '"'
if (date instanceof Date) {
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + quotes
}
return this.asString(date, skipQuotes)
}
asTimeNullable (date, skipQuotes) {
return date === null ? 'null' : this.asTime(date, skipQuotes)
}
asString (str, skipQuotes) {
const quotes = skipQuotes === true ? '' : '"'
if (str instanceof Date) {
return quotes + str.toISOString() + quotes
} else if (str === null) {
return quotes + quotes
} else if (str instanceof RegExp) {
str = str.source
} else if (typeof str !== 'string') {
str = str.toString()
}
// If we skipQuotes it means that we are using it as test
// no need to test the string length for the render
if (skipQuotes) {
return str
}
if (str.length < 42) {
return this.asStringSmall(str)
} else {
return JSON.stringify(str)
}
}
asStringNullable (str) {
return str === null ? 'null' : this.asString(str)
}
// magically escape strings for json
// relying on their charCodeAt
// everything below 32 needs JSON.stringify()
// every string that contain surrogate needs JSON.stringify()
// 34 and 92 happens all the time, so we
// have a fast case for them
asStringSmall (str) {
const l = str.length
let result = ''
let last = 0
let found = false
let surrogateFound = false
let point = 255
// eslint-disable-next-line
for (var i = 0; i < l && point >= 32; i++) {
point = str.charCodeAt(i)
if (point >= 0xD800 && point <= 0xDFFF) {
// The current character is a surrogate.
surrogateFound = true
}
if (point === 34 || point === 92) {
result += str.slice(last, i) + '\\'
last = i
found = true
}
}
if (!found) {
result = str
} else {
result += str.slice(last)
}
return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"'
}
}
function build (schema, options) {

@@ -70,2 +237,25 @@ arrayItemsReferenceSerializersMap.clear()

const validateDateTimeFormat = ajvFormats.get('date-time').validate
const validateDateFormat = ajvFormats.get('date').validate
const validateTimeFormat = ajvFormats.get('time').validate
ajvInstance.addKeyword({
keyword: 'fjs_date_type',
validate: (schema, date) => {
if (date instanceof Date) {
return true
}
if (schema === 'date-time') {
return validateDateTimeFormat(date)
}
if (schema === 'date') {
return validateDateFormat(date)
}
if (schema === 'time') {
return validateTimeFormat(date)
}
return false
}
})
isValidSchema(schema)

@@ -79,7 +269,4 @@ if (options.schema) {

let intParseFunctionName = 'trunc'
if (options.rounding) {
if (['floor', 'ceil', 'round'].includes(options.rounding)) {
intParseFunctionName = options.rounding
} else {
if (!['floor', 'ceil', 'round'].includes(options.rounding)) {
throw new Error(`Unsupported integer rounding method ${options.rounding}`)

@@ -105,12 +292,4 @@ }

/* eslint no-new-func: "off" */
let code = `
'use strict'
`
const serializer = new Serializer(options)
code += `
${asFunctions}
function parseInteger(int) { return Math.${intParseFunctionName}(int) }
`
let location = {

@@ -131,53 +310,25 @@ schema,

let main
const { code, laterCode } = buildValue('', 'main', 'input', location)
const contextFunctionCode = `
'use strict'
function main (input) {
let json = ''
${code}
return json
}
${laterCode}
return main
`
switch (schema.type) {
case 'object':
main = '$main'
code = buildObject(location, code, main)
break
case 'string':
main = schema.nullable ? '$asStringNullable' : getStringSerializer(schema.format)
break
case 'integer':
main = schema.nullable ? '$asIntegerNullable' : '$asInteger'
break
case 'number':
main = schema.nullable ? '$asNumberNullable' : '$asNumber'
break
case 'boolean':
main = schema.nullable ? '$asBooleanNullable' : '$asBoolean'
break
case 'null':
main = '$asNull'
break
case 'array':
main = '$main'
code = buildArray(location, code, main)
schema = location.schema
break
case undefined:
main = '$asAny'
break
default:
throw new Error(`${schema.type} unsupported`)
}
const dependenciesName = ['ajv', 'serializer', contextFunctionCode]
code += `
;
return ${main}
`
const dependencies = [ajvInstance]
const dependenciesName = ['ajv']
ajvInstance = null
dependenciesName.push(code)
if (options.debugMode) {
return {
code: dependenciesName.join('\n'),
ajv: dependencies[0]
}
return { code: dependenciesName.join('\n'), ajv: ajvInstance }
}
/* eslint no-new-func: "off" */
const contextFunc = new Function('ajv', 'serializer', contextFunctionCode)
const stringifyFunc = contextFunc(ajvInstance, serializer)
ajvInstance = null
arrayItemsReferenceSerializersMap.clear()

@@ -187,3 +338,3 @@ objectReferenceSerializersMap.clear()

return (Function.apply(null, dependenciesName).apply(null, dependencies))
return stringifyFunc
}

@@ -248,167 +399,11 @@

const stringSerializerMap = {
'date-time': '$asDatetime',
date: '$asDate',
time: '$asTime'
}
function getStringSerializer (format) {
return stringSerializerMap[format] ||
'$asString'
}
function getTestSerializer (format) {
return stringSerializerMap[format]
}
const asFunctions = `
function $pad2Zeros (num) {
const s = '00' + num
return s[s.length - 2] + s[s.length - 1]
}
function $asAny (i) {
return JSON.stringify(i)
}
function $asNull () {
return 'null'
}
function $asInteger (i) {
if (typeof i === 'bigint') {
return i.toString()
} else if (Number.isInteger(i)) {
return $asNumber(i)
} else {
/* eslint no-undef: "off" */
return $asNumber(parseInteger(i))
function getStringSerializer (format, nullable) {
switch (format) {
case 'date-time': return nullable ? 'serializer.asDatetimeNullable.bind(serializer)' : 'serializer.asDatetime.bind(serializer)'
case 'date': return nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
case 'time': return nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
default: return nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
}
}
function $asIntegerNullable (i) {
return i === null ? null : $asInteger(i)
}
function $asNumber (i) {
const num = Number(i)
if (isNaN(num)) {
return 'null'
} else {
return '' + num
}
}
function $asNumberNullable (i) {
return i === null ? null : $asNumber(i)
}
function $asBoolean (bool) {
return bool && 'true' || 'false' // eslint-disable-line
}
function $asBooleanNullable (bool) {
return bool === null ? null : $asBoolean(bool)
}
function $asDatetime (date, skipQuotes) {
const quotes = skipQuotes === true ? '' : '"'
if (date instanceof Date) {
return quotes + date.toISOString() + quotes
} else if (date && typeof date.toISOString === 'function') {
return quotes + date.toISOString() + quotes
} else {
return $asString(date, skipQuotes)
}
}
function $asDate (date, skipQuotes) {
const quotes = skipQuotes === true ? '' : '"'
if (date instanceof Date) {
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000 )).toISOString().slice(0, 10) + quotes
} else if (date && typeof date.format === 'function') {
return quotes + date.format('YYYY-MM-DD') + quotes
} else {
return $asString(date, skipQuotes)
}
}
function $asTime (date, skipQuotes) {
const quotes = skipQuotes === true ? '' : '"'
if (date instanceof Date) {
const hour = new Intl.DateTimeFormat('en', { hour: 'numeric', hour12: false }).format(date)
const minute = new Intl.DateTimeFormat('en', { minute: 'numeric' }).format(date)
const second = new Intl.DateTimeFormat('en', { second: 'numeric' }).format(date)
return quotes + $pad2Zeros(hour) + ':' + $pad2Zeros(minute) + ':' + $pad2Zeros(second) + quotes
} else if (date && typeof date.format === 'function') {
return quotes + date.format('HH:mm:ss') + quotes
} else {
return $asString(date, skipQuotes)
}
}
function $asString (str, skipQuotes) {
const quotes = skipQuotes === true ? '' : '"'
if (str instanceof Date) {
return quotes + str.toISOString() + quotes
} else if (str === null) {
return quotes + quotes
} else if (str instanceof RegExp) {
str = str.source
} else if (typeof str !== 'string') {
str = str.toString()
}
// If we skipQuotes it means that we are using it as test
// no need to test the string length for the render
if (skipQuotes) {
return str
}
if (str.length < 42) {
return $asStringSmall(str)
} else {
return JSON.stringify(str)
}
}
function $asStringNullable (str) {
return str === null ? null : $asString(str)
}
// magically escape strings for json
// relying on their charCodeAt
// everything below 32 needs JSON.stringify()
// every string that contain surrogate needs JSON.stringify()
// 34 and 92 happens all the time, so we
// have a fast case for them
function $asStringSmall (str) {
const l = str.length
let result = ''
let last = 0
let found = false
let surrogateFound = false
let point = 255
// eslint-disable-next-line
for (var i = 0; i < l && point >= 32; i++) {
point = str.charCodeAt(i)
if (point >= 0xD800 && point <= 0xDFFF) {
// The current character is a surrogate.
surrogateFound = true
}
if (point === 34 || point === 92) {
result += str.slice(last, i) + '\\\\'
last = i
found = true
}
}
if (!found) {
result = str
} else {
result += str.slice(last)
}
return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"'
}
`
function addPatternProperties (location) {

@@ -423,2 +418,4 @@ const schema = location.schema

`
let laterCode = ''
Object.keys(pp).forEach((regex, index) => {

@@ -430,5 +427,3 @@ let ppLocation = mergeLocation(location, { schema: pp[regex] })

}
const type = pp[regex].type
const format = pp[regex].format
const stringSerializer = getStringSerializer(format)
try {

@@ -440,66 +435,17 @@ RegExp(regex)

const ifPpKeyExists = `if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {`
if (type === 'object') {
code += `${buildObject(ppLocation, '', 'buildObjectPP' + index)}
${ifPpKeyExists}
${addComma}
json += $asString(keys[i]) + ':' + buildObjectPP${index}(obj[keys[i]])
`
} else if (type === 'array') {
code += `${buildArray(ppLocation, '', 'buildArrayPP' + index)}
${ifPpKeyExists}
${addComma}
json += $asString(keys[i]) + ':' + buildArrayPP${index}(obj[keys[i]])
`
} else if (type === 'null') {
code += `
${ifPpKeyExists}
${addComma}
json += $asString(keys[i]) +':null'
`
} else if (type === 'string') {
code += `
${ifPpKeyExists}
${addComma}
json += $asString(keys[i]) + ':' + ${stringSerializer}(obj[keys[i]])
`
} else if (type === 'integer') {
code += `
${ifPpKeyExists}
${addComma}
json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]])
`
} else if (type === 'number') {
code += `
${ifPpKeyExists}
${addComma}
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]])
`
} else if (type === 'boolean') {
code += `
${ifPpKeyExists}
${addComma}
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]])
`
} else if (type === undefined) {
code += `
${ifPpKeyExists}
${addComma}
json += $asString(keys[i]) + ':' + $asAny(obj[keys[i]])
`
} else {
code += `
${ifPpKeyExists}
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ' + ${JSON.stringify(type)})
`
}
const valueCode = buildValue('', '', 'obj[keys[i]]', ppLocation)
laterCode += valueCode.laterCode
code += `
continue
}
if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {
${addComma}
json += serializer.asString(keys[i]) + ':'
${valueCode.code}
continue
}
`
})
if (schema.additionalProperties) {
code += additionalProperty(location)
const additionalPropertyCode = additionalProperty(location)
code += additionalPropertyCode.code
laterCode += additionalPropertyCode.laterCode
}

@@ -510,3 +456,3 @@

`
return code
return { code, laterCode }
}

@@ -518,8 +464,10 @@

if (ap === true) {
return `
code += `
if (obj[keys[i]] !== undefined && typeof obj[keys[i]] !== 'function' && typeof obj[keys[i]] !== 'symbol') {
${addComma}
json += $asString(keys[i]) + ':' + JSON.stringify(obj[keys[i]])
json += serializer.asString(keys[i]) + ':' + JSON.stringify(obj[keys[i]])
}
`
return { code, laterCode: '' }
}

@@ -532,61 +480,16 @@ let apLocation = mergeLocation(location, { schema: ap })

const type = ap.type
const format = ap.format
const stringSerializer = getStringSerializer(format)
if (type === 'object') {
code += `${buildObject(apLocation, '', 'buildObjectAP')}
${addComma}
json += $asString(keys[i]) + ':' + buildObjectAP(obj[keys[i]])
`
} else if (type === 'array') {
code += `${buildArray(apLocation, '', 'buildArrayAP')}
${addComma}
json += $asString(keys[i]) + ':' + buildArrayAP(obj[keys[i]])
`
} else if (type === 'null') {
code += `
${addComma}
json += $asString(keys[i]) +':null'
`
} else if (type === 'string') {
code += `
${addComma}
json += $asString(keys[i]) + ':' + ${stringSerializer}(obj[keys[i]])
`
} else if (type === 'integer') {
code += `
var t = Number(obj[keys[i]])
if (!isNaN(t)) {
${addComma}
json += $asString(keys[i]) + ':' + t
}
`
} else if (type === 'number') {
code += `
var t = Number(obj[keys[i]])
if (!isNaN(t)) {
${addComma}
json += $asString(keys[i]) + ':' + t
}
`
} else if (type === 'boolean') {
code += `
${addComma}
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]])
`
} else if (type === undefined) {
code += `
${addComma}
json += $asString(keys[i]) + ':' + $asAny(obj[keys[i]])
`
} else {
code += `
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ' + ${JSON.stringify(type)})
`
}
return code
const valueCode = buildValue('', '', 'obj[keys[i]]', apLocation)
code += `
${addComma}
json += serializer.asString(keys[i]) + ':'
${valueCode.code}
`
return { code, laterCode: valueCode.laterCode }
}
function addAdditionalProperties (location) {
return `
const additionalPropertyCode = additionalProperty(location)
const code = `
var properties = ${JSON.stringify(location.schema.properties)} || {}

@@ -596,5 +499,7 @@ var keys = Object.keys(obj)

if (properties[keys[i]]) continue
${additionalProperty(location)}
${additionalPropertyCode.code}
}
`
return { code, laterCode: additionalPropertyCode.laterCode }
}

@@ -733,3 +638,3 @@

function buildCode (location, code, laterCode, name) {
function buildCode (location, code, laterCode, locationPath) {
if (location.schema.$ref) {

@@ -740,5 +645,5 @@ location = refFinder(location.schema.$ref, location)

const schema = location.schema
let required = schema.required
const required = schema.required || []
Object.keys(schema.properties || {}).forEach((key, i, a) => {
Object.keys(schema.properties || {}).forEach((key) => {
let propertyLocation = mergeLocation(location, { schema: schema.properties[key] })

@@ -753,50 +658,17 @@ if (schema.properties[key].$ref) {

const type = schema.properties[key].type
const nullable = schema.properties[key].nullable
const sanitized = JSON.stringify(key)
const asString = JSON.stringify(sanitized)
if (nullable) {
code += `
if (obj[${sanitized}] === null) {
${addComma}
json += ${asString} + ':null'
var rendered = true
} else {
code += `
if (obj[${sanitized}] !== undefined) {
${addComma}
json += ${asString} + ':'
`
}
if (type === 'number') {
code += `
var t = Number(obj[${sanitized}])
if (!isNaN(t)) {
${addComma}
json += ${asString} + ':' + t
`
} else if (type === 'integer') {
code += `
var rendered = false
var t = $asInteger(obj[${sanitized}])
if (!isNaN(t)) {
${addComma}
json += ${asString} + ':' + t
rendered = true
}
if (rendered) {
`
} else {
code += `
if (obj[${sanitized}] !== undefined) {
${addComma}
json += ${asString} + ':'
`
const result = buildValue(laterCode, locationPath + key, `obj[${JSON.stringify(key)}]`, mergeLocation(propertyLocation, { schema: schema.properties[key] }))
code += result.code
laterCode = result.laterCode
const result = nested(laterCode, name, key, mergeLocation(propertyLocation, { schema: schema.properties[key] }), undefined, false)
code += result.code
laterCode = result.laterCode
}
const defaultValue = schema.properties[key].default
if (defaultValue !== undefined) {
required = filterRequired(required, key)
code += `

@@ -807,4 +679,3 @@ } else {

`
} else if (required && required.indexOf(key) !== -1) {
required = filterRequired(required, key)
} else if (required.includes(key)) {
code += `

@@ -819,66 +690,113 @@ } else {

`
})
if (nullable) {
code += `
}
`
for (const requiredProperty of required) {
if (schema.properties && schema.properties[requiredProperty] !== undefined) continue
code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
}
return { code, laterCode }
}
function mergeAllOfSchema (location, schema, mergedSchema) {
for (let allOfSchema of schema.allOf) {
if (allOfSchema.$ref) {
allOfSchema = refFinder(allOfSchema.$ref, mergeLocation(location, { schema: allOfSchema })).schema
}
})
if (required && required.length > 0) {
code += 'var required = ['
// eslint-disable-next-line
for (var i = 0; i < required.length; i++) {
if (i > 0) {
code += ','
let allOfSchemaType = allOfSchema.type
if (allOfSchemaType === undefined) {
allOfSchemaType = inferTypeByKeyword(allOfSchema)
}
if (allOfSchemaType !== undefined) {
if (
mergedSchema.type !== undefined &&
mergedSchema.type !== allOfSchemaType
) {
throw new Error('allOf schemas have different type values')
}
code += `${JSON.stringify(required[i])}`
mergedSchema.type = allOfSchemaType
}
code += `]
for (var i = 0; i < required.length; i++) {
if (obj[required[i]] === undefined) throw new Error('"' + required[i] + '" is required!')
if (allOfSchema.format !== undefined) {
if (
mergedSchema.format !== undefined &&
mergedSchema.format !== allOfSchema.format
) {
throw new Error('allOf schemas have different format values')
}
`
}
mergedSchema.format = allOfSchema.format
}
if (schema.allOf) {
const builtCode = buildCodeWithAllOfs(location, code, laterCode, name)
code = builtCode.code
laterCode = builtCode.laterCode
}
if (allOfSchema.nullable !== undefined) {
if (
mergedSchema.nullable !== undefined &&
mergedSchema.nullable !== allOfSchema.nullable
) {
throw new Error('allOf schemas have different nullable values')
}
mergedSchema.nullable = allOfSchema.nullable
}
return { code, laterCode }
}
if (allOfSchema.properties !== undefined) {
if (mergedSchema.properties === undefined) {
mergedSchema.properties = {}
}
Object.assign(mergedSchema.properties, allOfSchema.properties)
}
function filterRequired (required, key) {
if (!required) {
return required
}
return required.filter(k => k !== key)
}
if (allOfSchema.additionalProperties !== undefined) {
if (mergedSchema.additionalProperties === undefined) {
mergedSchema.additionalProperties = {}
}
Object.assign(mergedSchema.additionalProperties, allOfSchema.additionalProperties)
}
function buildCodeWithAllOfs (location, code, laterCode, name) {
if (location.schema.allOf) {
location.schema.allOf.forEach((ss) => {
const builtCode = buildCodeWithAllOfs(mergeLocation(location, { schema: ss }), code, laterCode, name)
code = builtCode.code
laterCode = builtCode.laterCode
})
} else {
const builtCode = buildCode(location, code, laterCode, name)
if (allOfSchema.patternProperties !== undefined) {
if (mergedSchema.patternProperties === undefined) {
mergedSchema.patternProperties = {}
}
Object.assign(mergedSchema.patternProperties, allOfSchema.patternProperties)
}
code = builtCode.code
laterCode = builtCode.laterCode
if (allOfSchema.required !== undefined) {
if (mergedSchema.required === undefined) {
mergedSchema.required = []
}
mergedSchema.required.push(...allOfSchema.required)
}
if (allOfSchema.oneOf !== undefined) {
if (mergedSchema.oneOf === undefined) {
mergedSchema.oneOf = []
}
mergedSchema.oneOf.push(...allOfSchema.oneOf)
}
if (allOfSchema.anyOf !== undefined) {
if (mergedSchema.anyOf === undefined) {
mergedSchema.anyOf = []
}
mergedSchema.anyOf.push(...allOfSchema.anyOf)
}
if (allOfSchema.allOf !== undefined) {
mergeAllOfSchema(location, allOfSchema, mergedSchema)
}
}
return { code, laterCode }
delete mergedSchema.allOf
}
function buildInnerObject (location, name) {
function buildInnerObject (location, locationPath) {
const schema = location.schema
const result = buildCodeWithAllOfs(location, '', '', name)
const result = buildCode(location, '', '', locationPath)
if (schema.patternProperties) {
result.code += addPatternProperties(location)
const { code, laterCode } = addPatternProperties(location)
result.code += code
result.laterCode += laterCode
} else if (schema.additionalProperties && !schema.patternProperties) {
result.code += addAdditionalProperties(location)
const { code, laterCode } = addAdditionalProperties(location)
result.code += code
result.laterCode += laterCode
}

@@ -888,3 +806,3 @@ return result

function addIfThenElse (location, name) {
function addIfThenElse (location, locationPath) {
let code = ''

@@ -914,3 +832,3 @@ let r

if (merged.if && merged.then) {
innerR = addIfThenElse(mergedLocation, name + 'Then')
innerR = addIfThenElse(mergedLocation, locationPath + 'Then')
code += innerR.code

@@ -920,3 +838,3 @@ laterCode = innerR.laterCode

r = buildInnerObject(mergedLocation, name + 'Then')
r = buildInnerObject(mergedLocation, locationPath + 'Then')
code += r.code

@@ -936,3 +854,3 @@ laterCode += r.laterCode

if (merged.if && merged.then) {
innerR = addIfThenElse(mergedLocation, name + 'Else')
innerR = addIfThenElse(mergedLocation, locationPath + 'Else')
code += innerR.code

@@ -942,3 +860,3 @@ laterCode += innerR.laterCode

r = buildInnerObject(mergedLocation, name + 'Else')
r = buildInnerObject(mergedLocation, locationPath + 'Else')
code += r.code

@@ -960,3 +878,3 @@ laterCode += r.laterCode

function buildObject (location, code, name) {
function buildObject (location, code, functionName, locationPath) {
const schema = location.schema

@@ -967,3 +885,4 @@ if (schema.$id !== undefined) {

code += `
function ${name} (input) {
function ${functionName} (input) {
// ${locationPath}
`

@@ -978,3 +897,3 @@ if (schema.nullable) {

if (objectReferenceSerializersMap.has(schema) && objectReferenceSerializersMap.get(schema) !== name) {
if (objectReferenceSerializersMap.has(schema) && objectReferenceSerializersMap.get(schema) !== functionName) {
code += `

@@ -986,3 +905,3 @@ return ${objectReferenceSerializersMap.get(schema)}(input)

}
objectReferenceSerializersMap.set(schema, name)
objectReferenceSerializersMap.set(schema, functionName)

@@ -1000,5 +919,5 @@ code += `

`
r = addIfThenElse(location, name)
r = addIfThenElse(location, locationPath)
} else {
r = buildInnerObject(location, name)
r = buildInnerObject(location, locationPath)
}

@@ -1017,3 +936,3 @@

function buildArray (location, code, name, key = null) {
function buildArray (location, code, functionName, locationPath) {
let schema = location.schema

@@ -1024,3 +943,4 @@ if (schema.$id !== undefined) {

code += `
function ${name} (obj) {
function ${functionName} (obj) {
// ${locationPath}
`

@@ -1058,3 +978,3 @@ if (schema.nullable) {

}
arrayItemsReferenceSerializersMap.set(schema.items, name)
arrayItemsReferenceSerializersMap.set(schema.items, functionName)
}

@@ -1066,3 +986,3 @@

result = schema.items.reduce((res, item, i) => {
const tmpRes = nested(laterCode, name, accessor, mergeLocation(location, { schema: item }), i, true)
const tmpRes = buildValue(laterCode, locationPath + accessor + i, 'obj[i]', mergeLocation(location, { schema: item }))
const condition = `i === ${i} && ${buildArrayTypeCondition(item.type, accessor)}`

@@ -1080,3 +1000,3 @@ return {

if (schema.additionalItems) {
const tmpRes = nested(laterCode, name, accessor, mergeLocation(location, { schema: schema.items }), undefined, true)
const tmpRes = buildValue(laterCode, locationPath + accessor, 'obj[i]', mergeLocation(location, { schema: schema.items }))
result.code += `

@@ -1095,27 +1015,28 @@ else if (i >= ${schema.items.length}) {

} else {
result = nested(laterCode, name, accessor, mergeLocation(location, { schema: schema.items }), undefined, true)
result = buildValue(laterCode, locationPath + accessor, 'obj[i]', mergeLocation(location, { schema: schema.items }))
}
if (key) {
code += `
if(!Array.isArray(obj)) {
throw new TypeError(\`Property '${key}' should be of type array, received '$\{obj}' instead.\`)
code += `
if (!Array.isArray(obj)) {
throw new TypeError(\`The value '$\{obj}' does not match schema definition.\`)
}
`
`
code += 'const arrayLength = obj.length\n'
if (largeArrayMechanism !== 'default') {
if (largeArrayMechanism === 'json-stringify') {
code += `if (arrayLength && arrayLength >= ${largeArraySize}) return JSON.stringify(obj)\n`
} else {
throw new Error(`Unsupported large array mechanism ${largeArrayMechanism}`)
}
}
code += `
var l = obj.length
if (l && l >= ${largeArraySize}) {`
const concatSnippet = `
}
var jsonOutput= ''
for (var i = 0; i < l; i++) {
var json = ''
let jsonOutput= ''
for (let i = 0; i < arrayLength; i++) {
let json = ''
${result.code}
jsonOutput += json
if (json.length > 0 && i < l - 1) {
if (json.length > 0 && i < arrayLength - 1) {
jsonOutput += ','

@@ -1127,19 +1048,3 @@ }

switch (largeArrayMechanism) {
case 'default':
code += `
return \`[\${obj.map(${result.mapFnName}).join(',')}]\``
break
case 'json-stringify':
code += `
return JSON.stringify(obj)`
break
default:
throw new Error(`Unsupported large array mechanism ${largeArrayMechanism}`)
}
code += `
${concatSnippet}
${result.laterCode}

@@ -1212,22 +1117,8 @@ `

let strNameCounter = 0
function asFuncName (str) {
// only allow chars that can work
let rep = str.replace(/[^a-zA-Z0-9$_]/g, '')
if (rep.length === 0) {
return 'anan' + strNameCounter++
} else if (rep !== str) {
rep += strNameCounter++
}
return rep
let genFuncNameCounter = 0
function generateFuncName () {
return 'anonymous' + genFuncNameCounter++
}
function nested (laterCode, name, key, location, subKey, isArray) {
let code = ''
let funcName
subKey = subKey || ''
function buildValue (laterCode, locationPath, input, location) {
let schema = location.schema

@@ -1246,59 +1137,54 @@

if (schema.allOf) {
const mergedSchema = clone(schema)
mergeAllOfSchema(location, schema, mergedSchema)
schema = mergedSchema
location.schema = mergedSchema
}
const type = schema.type
const nullable = schema.nullable === true
const accessor = isArray ? key : `[${JSON.stringify(key)}]`
let code = ''
let funcName
switch (type) {
case 'null':
funcName = '$asNull'
code += `
json += $asNull()
json += serializer.asNull()
`
break
case 'string': {
funcName = '$asString'
const stringSerializer = getStringSerializer(schema.format)
code += nullable ? `json += obj${accessor} === null ? null : ${stringSerializer}(obj${accessor})` : `json += ${stringSerializer}(obj${accessor})`
funcName = getStringSerializer(schema.format, nullable)
code += `json += ${funcName}(${input})`
break
}
case 'integer':
funcName = '$asInteger'
code += nullable ? `json += obj${accessor} === null ? null : $asInteger(obj${accessor})` : `json += $asInteger(obj${accessor})`
funcName = nullable ? 'serializer.asIntegerNullable.bind(serializer)' : 'serializer.asInteger.bind(serializer)'
code += `json += ${funcName}(${input})`
break
case 'number':
funcName = '$asNumber'
code += nullable ? `json += obj${accessor} === null ? null : $asNumber(obj${accessor})` : `json += $asNumber(obj${accessor})`
funcName = nullable ? 'serializer.asNumberNullable.bind(serializer)' : 'serializer.asNumber.bind(serializer)'
code += `json += ${funcName}(${input})`
break
case 'boolean':
funcName = '$asBoolean'
code += nullable ? `json += obj${accessor} === null ? null : $asBoolean(obj${accessor})` : `json += $asBoolean(obj${accessor})`
funcName = nullable ? 'serializer.asBooleanNullable.bind(serializer)' : 'serializer.asBoolean.bind(serializer)'
code += `json += ${funcName}(${input})`
break
case 'object':
funcName = asFuncName(name + key + subKey)
laterCode = buildObject(location, laterCode, funcName)
code += `
json += ${funcName}(obj${accessor})
`
funcName = generateFuncName()
laterCode = buildObject(location, laterCode, funcName, locationPath)
code += `json += ${funcName}(${input})`
break
case 'array':
funcName = asFuncName('$arr' + name + key + subKey) // eslint-disable-line
laterCode = buildArray(location, laterCode, funcName, key)
code += `
json += ${funcName}(obj${accessor})
`
funcName = generateFuncName()
laterCode = buildArray(location, laterCode, funcName, locationPath)
code += `json += ${funcName}(${input})`
break
case undefined:
funcName = '$asNull'
if ('anyOf' in schema) {
if (schema.anyOf || schema.oneOf) {
// beware: dereferenceOfRefs has side effects and changes schema.anyOf
const anyOfLocations = dereferenceOfRefs(location, 'anyOf')
anyOfLocations.forEach((location, index) => {
const nestedResult = nested(laterCode, name, key, location, subKey !== '' ? subKey : 'i' + index, isArray)
// We need a test serializer as the String serializer will not work with
// date/time ajv validations
// see: https://github.com/fastify/fast-json-stringify/issues/325
const testSerializer = getTestSerializer(location.schema.format)
const testValue = testSerializer !== undefined ? `${testSerializer}(obj${accessor}, true)` : `obj${accessor}`
const locations = dereferenceOfRefs(location, schema.anyOf ? 'anyOf' : 'oneOf')
locations.forEach((location, index) => {
const nestedResult = buildValue(laterCode, locationPath + 'i' + index, input, location)
// Since we are only passing the relevant schema to ajv.validate, it needs to be full dereferenced

@@ -1309,10 +1195,16 @@ // otherwise any $ref pointing to an external schema would result in an error.

// with the actual schema
// 2. `nested`, through `buildCode`, replaces any reference in object properties with the actual schema
// 2. `buildValue`, through `buildCode`, replaces any reference in object properties with the actual schema
// (see https://github.com/fastify/fast-json-stringify/blob/6da3b3e8ac24b1ca5578223adedb4083b7adf8db/index.js#L631)
// Ajv does not support js date format. In order to properly validate objects containing a date,
// it needs to replace all occurrences of the string date format with a custom keyword fjs_date_type.
// (see https://github.com/fastify/fast-json-stringify/pull/441)
const extendedSchema = clone(location.schema)
extendDateTimeType(extendedSchema)
const schemaKey = location.schema.$id || randomUUID()
ajvInstance.addSchema(location.schema, schemaKey)
ajvInstance.addSchema(extendedSchema, schemaKey)
code += `
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaKey}", ${testValue}))
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaKey}", ${input}))
${nestedResult.code}

@@ -1322,43 +1214,20 @@ `

})
code += `
else json+= null
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
`
} else if ('oneOf' in schema) {
// beware: dereferenceOfRefs has side effects and changes schema.oneOf
const oneOfLocations = dereferenceOfRefs(location, 'oneOf')
oneOfLocations.forEach((location, index) => {
const nestedResult = nested(laterCode, name, key, location, subKey !== '' ? subKey : 'i' + index, isArray)
const testSerializer = getTestSerializer(location.schema.format)
const testValue = testSerializer !== undefined ? `${testSerializer}(obj${accessor}, true)` : `obj${accessor}`
// see comment on anyOf about dereferencing the schema before calling ajv.validate
const schemaKey = location.schema.$id || randomUUID()
ajvInstance.addSchema(location.schema, schemaKey)
code += `
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaKey}", ${testValue}))
${nestedResult.code}
`
laterCode = nestedResult.laterCode
})
if (!isArray) {
code += `
else json+= null
`
}
} else if (isEmpty(schema)) {
code += `
json += JSON.stringify(obj${accessor})
json += JSON.stringify(${input})
`
} else if ('const' in schema) {
code += `
if(ajv.validate(${JSON.stringify(schema)}, obj${accessor}))
if(ajv.validate(${JSON.stringify(schema)}, ${input}))
json += '${JSON.stringify(schema.const)}'
else
throw new Error(\`Item $\{JSON.stringify(obj${accessor})} does not match schema definition.\`)
throw new Error(\`Item $\{JSON.stringify(${input})} does not match schema definition.\`)
`
} else if (schema.type === undefined) {
code += `
json += JSON.stringify(obj${accessor})
json += JSON.stringify(${input})
`

@@ -1371,12 +1240,21 @@ } else {

if (Array.isArray(type)) {
const nullIndex = type.indexOf('null')
const sortedTypes = nullIndex !== -1 ? [type[nullIndex]].concat(type.slice(0, nullIndex)).concat(type.slice(nullIndex + 1)) : type
let sortedTypes = type
const nullable = schema.nullable === true || type.includes('null')
if (nullable) {
sortedTypes = sortedTypes.filter(type => type !== 'null')
code += `
if (${input} === null) {
json += null
} else {`
}
sortedTypes.forEach((type, index) => {
const statement = index === 0 ? 'if' : 'else if'
const tempSchema = Object.assign({}, schema, { type })
const nestedResult = nested(laterCode, name, key, mergeLocation(location, { schema: tempSchema }), subKey, isArray)
const nestedResult = buildValue(laterCode, locationPath, input, mergeLocation(location, { schema: tempSchema }))
switch (type) {
case 'string': {
code += `
${statement}(obj${accessor} === null || typeof obj${accessor} === "${type}" || obj${accessor} instanceof Date || typeof obj${accessor}.toISOString === "function" || obj${accessor} instanceof RegExp || (typeof obj${accessor} === "object" && Object.hasOwnProperty.call(obj${accessor}, "toString")))
${statement}(${input} === null || typeof ${input} === "${type}" || ${input} instanceof Date || ${input} instanceof RegExp || (typeof ${input} === "object" && Object.hasOwnProperty.call(${input}, "toString")))
${nestedResult.code}

@@ -1386,12 +1264,5 @@ `

}
case 'null': {
code += `
${statement}(obj${accessor} == null)
${nestedResult.code}
`
break
}
case 'array': {
code += `
${statement}(Array.isArray(obj${accessor}))
${statement}(Array.isArray(${input}))
${nestedResult.code}

@@ -1403,3 +1274,3 @@ `

code += `
${statement}(Number.isInteger(obj${accessor}) || obj${accessor} === null)
${statement}(Number.isInteger(${input}) || ${input} === null)
${nestedResult.code}

@@ -1409,12 +1280,5 @@ `

}
case 'number': {
code += `
${statement}(isNaN(obj${accessor}) === false)
${nestedResult.code}
`
break
}
default: {
code += `
${statement}(typeof obj${accessor} === "${type}")
${statement}(typeof ${input} === "${type}" || ${input} === null)
${nestedResult.code}

@@ -1428,4 +1292,10 @@ `

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

@@ -1436,7 +1306,16 @@ throw new Error(`${type} unsupported`)

return {
code,
laterCode,
mapFnName: funcName
return { code, laterCode }
}
function extendDateTimeType (schema) {
if (schema.type === 'string' && ['date-time', 'date', 'time'].includes(schema.format)) {
schema.fjs_date_type = schema.format
delete schema.type
delete schema.format
}
for (const property in schema) {
if (typeof schema[property] === 'object') {
extendDateTimeType(schema[property])
}
}
}

@@ -1459,5 +1338,6 @@

module.exports.restore = function ({ code, ajv }) {
const serializer = new Serializer()
// eslint-disable-next-line
return (Function.apply(null, ['ajv', code])
.apply(null, [ajv]))
return (Function.apply(null, ['ajv', 'serializer', code])
.apply(null, [ajv, serializer]))
}
{
"name": "fast-json-stringify",
"version": "3.2.0",
"version": "4.0.0",
"description": "Stringify your JSON at max speed",

@@ -8,3 +8,6 @@ "main": "index.js",

"scripts": {
"benchmark": "node bench.js",
"bench": "node ./benchmark/bench.js",
"bench:cmp": "node ./benchmark/bench-cmp-branch.js",
"bench:cmp:ci": "node ./benchmark/bench-cmp-branch.js --ci",
"benchmark": "node ./benchmark/bench-cmp-lib.js",
"lint:fix": "standard --fix",

@@ -36,8 +39,11 @@ "test:lint": "standard",

"benchmark": "^2.1.4",
"chalk": "^4.1.2",
"compile-json-stringify": "^0.1.2",
"inquirer": "^8.2.4",
"is-my-json-valid": "^2.20.0",
"moment": "^2.24.0",
"luxon": "^2.4.0",
"pre-commit": "^1.2.2",
"proxyquire": "^2.1.3",
"semver": "^7.1.0",
"simple-git": "^3.7.1",
"standard": "^17.0.0",

@@ -52,3 +58,3 @@ "tap": "^16.0.1",

"deepmerge": "^4.2.2",
"fast-uri": "^1.0.1",
"fast-uri": "^2.0.0",
"rfdc": "^1.2.0",

@@ -55,0 +61,0 @@ "string-similarity": "^4.0.1"

@@ -5,3 +5,2 @@ # fast-json-stringify

[![NPM version](https://img.shields.io/npm/v/fast-json-stringify.svg?style=flat)](https://www.npmjs.com/package/fast-json-stringify)
[![Known Vulnerabilities](https://snyk.io/test/github/fastify/fast-json-stringify/badge.svg)](https://snyk.io/test/github/fastify/fast-json-stringify)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)

@@ -11,4 +10,4 @@ [![NPM downloads](https://img.shields.io/npm/dm/fast-json-stringify.svg?style=flat)](https://www.npmjs.com/package/fast-json-stringify)

__fast-json-stringify__ is significantly faster than `JSON.stringify()` for small payloads.
Its performance advantage shrinks as your payload grows.
__fast-json-stringify__ is significantly faster than `JSON.stringify()` for small payloads.
Its performance advantage shrinks as your payload grows.
It pairs well with [__flatstr__](https://www.npmjs.com/package/flatstr), which triggers a V8 optimization that improves performance when eventually converting the string to a `Buffer`.

@@ -165,7 +164,5 @@

Example with a MomentJS object:
Example with a Date object:
```javascript
const moment = require('moment')
const stringify = fastJson({

@@ -177,3 +174,4 @@ title: 'Example Schema with string date-time field',

console.log(stringify(moment())) // '"YYYY-MM-DDTHH:mm:ss.sssZ"'
const date = new Date()
console.log(stringify(date)) // '"YYYY-MM-DDTHH:mm:ss.sssZ"'
```

@@ -294,3 +292,3 @@

If *additionalProperties* is set to `true`, it will be used by `JSON.stringify` to stringify the additional properties. If you want to achieve maximum performance, we strongly encourage you to use a fixed schema where possible.
The additional properties will always be serialzied at the end of the object.
The additional properties will always be serialized at the end of the object.
Example:

@@ -297,0 +295,0 @@ ```javascript

@@ -22,3 +22,3 @@ 'use strict'

const obj = { str: 'test', foo: 42, ofoo: true, foof: 'string', objfoo: { a: true } }
t.equal('{"str":"test","foo":"42","ofoo":"true","foof":"string","objfoo":"[object Object]"}', stringify(obj))
t.equal(stringify(obj), '{"str":"test","foo":"42","ofoo":"true","foof":"string","objfoo":"[object Object]"}')
})

@@ -42,3 +42,3 @@

const obj = { foo: '42', ofoo: 42 }
t.equal('{"foo":"42","ofoo":42}', stringify(obj))
t.equal(stringify(obj), '{"foo":"42","ofoo":42}')
})

@@ -67,3 +67,3 @@

const obj = { foo: '42', ofoo: 42, test: '42' }
t.equal('{"foo":"42","ofoo":"42","test":42}', stringify(obj))
t.equal(stringify(obj), '{"foo":"42","ofoo":"42","test":42}')
})

@@ -81,3 +81,3 @@

const obj = { foo: true, ofoo: 42, arrfoo: ['array', 'test'], objfoo: { a: 'world' } }
t.equal('{"foo":true,"ofoo":42,"arrfoo":["array","test"],"objfoo":{"a":"world"}}', stringify(obj))
t.equal(stringify(obj), '{"foo":true,"ofoo":42,"arrfoo":["array","test"],"objfoo":{"a":"world"}}')
})

@@ -97,3 +97,3 @@

const obj = { foo: true, ofoo: 42, arrfoo: ['array', 'test'], objfoo: { a: 'world' } }
t.equal('{"foo":"true","ofoo":"42","arrfoo":"array,test","objfoo":"[object Object]"}', stringify(obj))
t.equal(stringify(obj), '{"foo":"true","ofoo":"42","arrfoo":"array,test","objfoo":"[object Object]"}')
})

@@ -112,3 +112,4 @@

const obj = { foo: true, ofoo: '42', xfoo: 'string', arrfoo: [1, 2], objfoo: { num: 42 } }
// const obj = { foo: true, ofoo: '42', xfoo: 'string', arrfoo: [1, 2], objfoo: { num: 42 } }
const obj = { foo: true, ofoo: '42' }
t.equal(stringify(obj), '{"foo":1,"ofoo":42}')

@@ -149,7 +150,7 @@ })

const obj = { objfoo: { answer: 42 } }
t.equal('{"objfoo":{"answer":42}}', stringify(obj))
t.equal(stringify(obj), '{"objfoo":{"answer":42}}')
})
test('additionalProperties - array coerce', (t) => {
t.plan(1)
t.plan(2)
const stringify = build({

@@ -167,4 +168,7 @@ title: 'check array coerce',

const obj = { foo: 'true', ofoo: 0, arrfoo: [1, 2], objfoo: { tyrion: 'lannister' } }
t.equal('{"foo":["t","r","u","e"],"ofoo":[],"arrfoo":["1","2"],"objfoo":[]}', stringify(obj))
const coercibleValues = { arrfoo: [1, 2] }
t.equal(stringify(coercibleValues), '{"arrfoo":["1","2"]}')
const incoercibleValues = { foo: 'true', ofoo: 0, objfoo: { tyrion: 'lannister' } }
t.throws(() => stringify(incoercibleValues))
})

@@ -180,3 +184,3 @@

const obj = { a: 1, b: true, c: null }
t.equal('{"a":1,"b":true,"c":null}', stringify(obj))
t.equal(stringify(obj), '{"a":1,"b":true,"c":null}')
})

@@ -195,3 +199,3 @@

const obj = { data: { a: 1, b: true, c: null } }
t.equal('{"data":{"a":1,"b":true,"c":null}}', stringify(obj))
t.equal(stringify(obj), '{"data":{"a":1,"b":true,"c":null}}')
})

@@ -216,3 +220,3 @@

const obj = [{ ap: { value: 'string' } }]
t.equal('[{"ap":{"value":"string"}}]', stringify(obj))
t.equal(stringify(obj), '[{"ap":{"value":"string"}}]')
})

@@ -254,3 +258,3 @@

const obj = [{ ap: { nested: { moarNested: { finally: { value: 'str' } } } } }]
t.equal('[{"ap":{"nested":{"moarNested":{"finally":{"value":"str"}}}}}]', stringify(obj))
t.equal(stringify(obj), '[{"ap":{"nested":{"moarNested":{"finally":{"value":"str"}}}}}]')
})

@@ -272,3 +276,3 @@

const obj = { ap: { value: 'string', someNumber: 42 } }
t.equal('{"ap":{"value":"string","someNumber":42}}', stringify(obj))
t.equal(stringify(obj), '{"ap":{"value":"string","someNumber":42}}')
})

@@ -290,3 +294,3 @@

const obj = { ap: { value: 'string', someNumber: undefined } }
t.equal('{"ap":{"value":"string"}}', stringify(obj))
t.equal(stringify(obj), '{"ap":{"value":"string"}}')
})

@@ -307,3 +311,3 @@

const obj = { ap: { additional: 'field' } }
t.equal('{"ap":{"additional":"field"}}', stringify(obj))
t.equal(stringify(obj), '{"ap":{"additional":"field"}}')
})

@@ -325,3 +329,3 @@

const obj = { ap: { additional: 'field' } }
t.equal('{"ap":{}}', stringify(obj))
t.equal(stringify(obj), '{"ap":{}}')
})

@@ -343,3 +347,3 @@

const obj = { str: 'x', test: 'test', meth: () => 'x', sym: Symbol('x') }
t.equal('{"str":"x","test":"test"}', stringify(obj))
t.equal(stringify(obj), '{"str":"x","test":"test"}')
})
'use strict'
const test = require('tap').test
const { DateTime } = require('luxon')
const build = require('..')
test('allOf: combine type and format ', (t) => {
t.plan(1)
const schema = {
allOf: [
{ type: 'string' },
{ format: 'time' }
]
}
const stringify = build(schema)
const date = new Date()
const value = stringify(date)
t.equal(value, `"${DateTime.fromJSDate(date).toFormat('HH:mm:ss')}"`)
})
test('allOf: combine additional properties ', (t) => {
t.plan(1)
const schema = {
allOf: [
{ type: 'object' },
{
type: 'object',
additionalProperties: { type: 'boolean' }
}
]
}
const stringify = build(schema)
const data = { property: true }
const value = stringify(data)
t.equal(value, JSON.stringify(data))
})
test('allOf: combine pattern properties', (t) => {
t.plan(1)
const schema = {
allOf: [
{ type: 'object' },
{
type: 'object',
patternProperties: {
foo: {
type: 'number'
}
}
}
]
}
const stringify = build(schema)
const data = { foo: 42 }
const value = stringify(data)
t.equal(value, JSON.stringify(data))
})
test('object with allOf and multiple schema on the allOf', (t) => {

@@ -7,0 +63,0 @@ t.plan(4)

'use strict'
const { DateTime } = require('luxon')
const { test } = require('tap')

@@ -116,7 +117,3 @@ const build = require('..')

const stringify = build(schema)
const value = stringify({
str: 1
})
t.equal(value, '{"str":null}')
t.throws(() => stringify({ str: 1 }))
})

@@ -228,3 +225,3 @@

t.equal(stringify({ value: 'baz' }), '{"value":"baz"}')
t.equal(stringify({ value: 'qux' }), '{"value":null}')
t.throws(() => stringify({ value: 'qux' }))
})

@@ -477,2 +474,86 @@

test('anyOf object with nested field date-time of type string with format or null', (t) => {
t.plan(1)
const withOneOfSchema = {
type: 'object',
properties: {
prop: {
anyOf: [{
type: 'object',
properties: {
nestedProp: {
type: 'string',
format: 'date-time'
}
}
}]
}
}
}
const withOneOfStringify = build(withOneOfSchema)
const data = {
prop: { nestedProp: new Date() }
}
t.equal(withOneOfStringify(data), JSON.stringify(data))
})
test('anyOf object with nested field date of type string with format or null', (t) => {
t.plan(1)
const withOneOfSchema = {
type: 'object',
properties: {
prop: {
anyOf: [{
type: 'object',
properties: {
nestedProp: {
type: 'string',
format: 'date'
}
}
}]
}
}
}
const withOneOfStringify = build(withOneOfSchema)
const data = {
prop: { nestedProp: new Date() }
}
t.equal(withOneOfStringify(data), `{"prop":{"nestedProp":"${DateTime.fromJSDate(data.prop.nestedProp).toISODate()}"}}`)
})
test('anyOf object with nested field time of type string with format or null', (t) => {
t.plan(1)
const withOneOfSchema = {
type: 'object',
properties: {
prop: {
anyOf: [{
type: 'object',
properties: {
nestedProp: {
type: 'string',
format: 'time'
}
}
}]
}
}
}
const withOneOfStringify = build(withOneOfSchema)
const data = {
prop: { nestedProp: new Date() }
}
t.equal(withOneOfStringify(data), `{"prop":{"nestedProp":"${DateTime.fromJSDate(data.prop.nestedProp).toFormat('HH:mm:ss')}"}}`)
})
test('anyOf object with field date of type string with format or null', (t) => {

@@ -519,5 +600,3 @@ t.plan(1)

const withOneOfStringify = build(withOneOfSchema)
t.equal(withOneOfStringify({
prop: toStringify
}), '{"prop":null}')
t.throws(() => withOneOfStringify({ prop: toStringify }))
})
'use strict'
const moment = require('moment')
const test = require('tap').test

@@ -180,23 +179,2 @@ const validator = require('is-my-json-valid')

test('moment array', (t) => {
t.plan(1)
const schema = {
type: 'object',
properties: {
times: {
type: 'array',
items: {
type: 'string',
format: 'date-time'
}
}
}
}
const stringify = build(schema)
const value = stringify({
times: [moment('2018-04-21T07:52:31.017Z')]
})
t.equal(value, '{"times":["2018-04-21T07:52:31.017Z"]}')
})
buildTest({

@@ -348,3 +326,3 @@ title: 'item types in array default to any',

buildTest({
title: 'large array with json-stringify mechanism',
title: 'large array of objects with json-stringify mechanism',
type: 'object',

@@ -368,1 +346,81 @@ properties: {

})
buildTest({
title: 'large array of strings with default mechanism',
type: 'object',
properties: {
ids: {
type: 'array',
items: { type: 'string' }
}
}
}, {
ids: new Array(2e4).fill('string')
}, {
largeArraySize: 2e4,
largeArrayMechanism: 'default'
})
buildTest({
title: 'large array of numbers with default mechanism',
type: 'object',
properties: {
ids: {
type: 'array',
items: { type: 'number' }
}
}
}, {
ids: new Array(2e4).fill(42)
}, {
largeArraySize: 2e4,
largeArrayMechanism: 'default'
})
buildTest({
title: 'large array of integers with default mechanism',
type: 'object',
properties: {
ids: {
type: 'array',
items: { type: 'integer' }
}
}
}, {
ids: new Array(2e4).fill(42)
}, {
largeArraySize: 2e4,
largeArrayMechanism: 'default'
})
buildTest({
title: 'large array of booleans with default mechanism',
type: 'object',
properties: {
ids: {
type: 'array',
items: { type: 'boolean' }
}
}
}, {
ids: new Array(2e4).fill(true)
}, {
largeArraySize: 2e4,
largeArrayMechanism: 'default'
})
buildTest({
title: 'large array of null values with default mechanism',
type: 'object',
properties: {
ids: {
type: 'array',
items: { type: 'null' }
}
}
}, {
ids: new Array(2e4).fill(null)
}, {
largeArraySize: 2e4,
largeArrayMechanism: 'default'
})

@@ -264,3 +264,3 @@ 'use strict'

test('skip or coerce numbers and integers that are not numbers', (t) => {
test('throw an error or coerce numbers and integers that are not numbers', (t) => {
const stringify = build({

@@ -279,10 +279,10 @@ title: 'basic',

let result = stringify({
age: 'hello ',
distance: 'long'
})
try {
stringify({ age: 'hello ', distance: 'long' })
t.fail('should throw an error')
} catch (err) {
t.ok(err)
}
t.same(JSON.parse(result), {})
result = stringify({
const result = stringify({
age: '42',

@@ -289,0 +289,0 @@ distance: true

'use strict'
const test = require('tap').test
const moment = require('moment')
const { DateTime } = require('luxon')
const validator = require('is-my-json-valid')

@@ -43,3 +43,3 @@ const build = require('..')

test('render a date in a string when format is date as YYYY-MM-DD', (t) => {
test('render a nullable date in a string when format is date-format as ISOString', (t) => {
t.plan(2)

@@ -50,3 +50,4 @@

type: 'string',
format: 'date'
format: 'date-time',
nullable: true
}

@@ -59,7 +60,7 @@ const toStringify = new Date()

t.equal(output, `"${moment(toStringify).format('YYYY-MM-DD')}"`)
t.equal(output, JSON.stringify(toStringify))
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('verify padding for rendered date in a string when format is date', (t) => {
test('render a date in a string when format is date as YYYY-MM-DD', (t) => {
t.plan(2)

@@ -72,3 +73,3 @@

}
const toStringify = new Date(2020, 0, 1, 0, 0, 0, 0)
const toStringify = new Date()

@@ -79,8 +80,8 @@ const validate = validator(schema)

t.equal(output, `"${moment(toStringify).format('YYYY-MM-DD')}"`)
t.equal(output, `"${DateTime.fromJSDate(toStringify).toISODate()}"`)
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('render a date in a string when format is time as kk:mm:ss', (t) => {
t.plan(3)
test('render a nullable date in a string when format is date as YYYY-MM-DD', (t) => {
t.plan(2)

@@ -90,3 +91,4 @@ const schema = {

type: 'string',
format: 'time'
format: 'date',
nullable: true
}

@@ -99,10 +101,25 @@ const toStringify = new Date()

validate(JSON.parse(output))
t.equal(validate.errors, null)
t.equal(output, `"${DateTime.fromJSDate(toStringify).toISODate()}"`)
t.ok(validate(JSON.parse(output)), 'valid schema')
})
t.equal(output, `"${moment(toStringify).format('HH:mm:ss')}"`)
test('verify padding for rendered date in a string when format is date', (t) => {
t.plan(2)
const schema = {
title: 'a date in a string',
type: 'string',
format: 'date'
}
const toStringify = new Date(2020, 0, 1, 0, 0, 0, 0)
const validate = validator(schema)
const stringify = build(schema)
const output = stringify(toStringify)
t.equal(output, `"${DateTime.fromJSDate(toStringify).toISODate()}"`)
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('verify padding for rendered date in a string when format is time', (t) => {
test('render a date in a string when format is time as kk:mm:ss', (t) => {
t.plan(3)

@@ -115,3 +132,3 @@

}
const toStringify = new Date(2020, 0, 1, 1, 1, 1, 1)
const toStringify = new Date()

@@ -125,15 +142,16 @@ const validate = validator(schema)

t.equal(output, `"${moment(toStringify).format('HH:mm:ss')}"`)
t.equal(output, `"${DateTime.fromJSDate(toStringify).toFormat('HH:mm:ss')}"`)
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('render a moment.js instance in a string when format is date-time as ISOString', (t) => {
t.plan(2)
test('render a nullable date in a string when format is time as kk:mm:ss', (t) => {
t.plan(3)
const schema = {
title: 'a moment.js object in a string',
title: 'a date in a string',
type: 'string',
format: 'date-time'
format: 'time',
nullable: true
}
const toStringify = moment()
const toStringify = new Date()

@@ -144,33 +162,39 @@ const validate = validator(schema)

t.equal(output, JSON.stringify(toStringify))
validate(JSON.parse(output))
t.equal(validate.errors, null)
t.equal(output, `"${DateTime.fromJSDate(toStringify).toFormat('HH:mm:ss')}"`)
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('render a moment.js instance in a string when format is date as YYYY-MM-DD', (t) => {
t.plan(2)
test('render a midnight time', (t) => {
t.plan(3)
const schema = {
title: 'a moment.js object in a string',
title: 'a date in a string',
type: 'string',
format: 'date'
format: 'time'
}
const toStringify = moment()
const midnight = new Date(new Date().setHours(24))
const validate = validator(schema)
const stringify = build(schema)
const output = stringify(toStringify)
const output = stringify(midnight)
t.equal(output, `"${toStringify.format('YYYY-MM-DD')}"`)
validate(JSON.parse(output))
t.equal(validate.errors, null)
t.equal(output, `"${DateTime.fromJSDate(midnight).toFormat('HH:mm:ss')}"`)
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('render a moment.js instance in a string when format is time as HH:mm:ss', (t) => {
t.plan(2)
test('verify padding for rendered date in a string when format is time', (t) => {
t.plan(3)
const schema = {
title: 'a moment.js object in a string',
title: 'a date in a string',
type: 'string',
format: 'time'
}
const toStringify = moment()
const toStringify = new Date(2020, 0, 1, 1, 1, 1, 1)

@@ -181,3 +205,6 @@ const validate = validator(schema)

t.equal(output, `"${toStringify.format('HH:mm:ss')}"`)
validate(JSON.parse(output))
t.equal(validate.errors, null)
t.equal(output, `"${DateTime.fromJSDate(toStringify).toFormat('HH:mm:ss')}"`)
t.ok(validate(JSON.parse(output)), 'valid schema')

@@ -199,3 +226,3 @@ })

}
const toStringify = { date: moment() }
const toStringify = { date: new Date() }

@@ -202,0 +229,0 @@ const validate = validator(schema)

@@ -101,3 +101,3 @@ 'use strict'

t.same(compiled({ str: 'foo@bar.com' }), tobe)
t.same(compiled({ str: 'foo' }), JSON.stringify({ str: null }), 'invalid format is ignored')
t.throws(() => compiled({ str: 'foo' }))
})

@@ -33,1 +33,32 @@ 'use strict'

})
test('names collision', (t) => {
t.plan(1)
const schema = {
title: 'nested objects with same properties',
type: 'object',
properties: {
test: {
type: 'object',
properties: {
a: { type: 'string' }
}
},
tes: {
type: 'object',
properties: {
b: { type: 'string' },
t: { type: 'object' }
}
}
}
}
const stringify = build(schema)
const data = {
test: { a: 'a' },
tes: { b: 'b', t: {} }
}
t.equal(stringify(data), JSON.stringify(data))
})

@@ -119,1 +119,329 @@ 'use strict'

})
test('handle nullable number correctly', (t) => {
t.plan(2)
const schema = {
type: 'number',
nullable: true
}
const stringify = build(schema)
const data = null
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})
test('handle nullable integer correctly', (t) => {
t.plan(2)
const schema = {
type: 'integer',
nullable: true
}
const stringify = build(schema)
const data = null
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})
test('handle nullable boolean correctly', (t) => {
t.plan(2)
const schema = {
type: 'boolean',
nullable: true
}
const stringify = build(schema)
const data = null
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})
test('handle nullable string correctly', (t) => {
t.plan(2)
const schema = {
type: 'string',
nullable: true
}
const stringify = build(schema)
const data = null
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})
test('handle nullable date-time correctly', (t) => {
t.plan(2)
const schema = {
type: 'string',
format: 'date-time',
nullable: true
}
const stringify = build(schema)
const data = null
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})
test('handle nullable date correctly', (t) => {
t.plan(2)
const schema = {
type: 'string',
format: 'date',
nullable: true
}
const stringify = build(schema)
const data = null
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})
test('handle nullable time correctly', (t) => {
t.plan(2)
const schema = {
type: 'string',
format: 'time',
nullable: true
}
const stringify = build(schema)
const data = null
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})
test('large array of nullable strings with default mechanism', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
ids: {
type: 'array',
items: {
type: 'string',
nullable: true
}
}
}
}
const options = {
largeArraySize: 2e4,
largeArrayMechanism: 'default'
}
const stringify = build(schema, options)
const data = { ids: new Array(2e4).fill(null) }
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})
test('large array of nullable date-time strings with default mechanism', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
ids: {
type: 'array',
items: {
type: 'string',
format: 'date-time',
nullable: true
}
}
}
}
const options = {
largeArraySize: 2e4,
largeArrayMechanism: 'default'
}
const stringify = build(schema, options)
const data = { ids: new Array(2e4).fill(null) }
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})
test('large array of nullable date-time strings with default mechanism', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
ids: {
type: 'array',
items: {
type: 'string',
format: 'date',
nullable: true
}
}
}
}
const options = {
largeArraySize: 2e4,
largeArrayMechanism: 'default'
}
const stringify = build(schema, options)
const data = { ids: new Array(2e4).fill(null) }
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})
test('large array of nullable date-time strings with default mechanism', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
ids: {
type: 'array',
items: {
type: 'string',
format: 'time',
nullable: true
}
}
}
}
const options = {
largeArraySize: 2e4,
largeArrayMechanism: 'default'
}
const stringify = build(schema, options)
const data = { ids: new Array(2e4).fill(null) }
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})
test('large array of nullable numbers with default mechanism', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
ids: {
type: 'array',
items: {
type: 'number',
nullable: true
}
}
}
}
const options = {
largeArraySize: 2e4,
largeArrayMechanism: 'default'
}
const stringify = build(schema, options)
const data = { ids: new Array(2e4).fill(null) }
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})
test('large array of nullable integers with default mechanism', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
ids: {
type: 'array',
items: {
type: 'integer',
nullable: true
}
}
}
}
const options = {
largeArraySize: 2e4,
largeArrayMechanism: 'default'
}
const stringify = build(schema, options)
const data = { ids: new Array(2e4).fill(null) }
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})
test('large array of nullable booleans with default mechanism', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
ids: {
type: 'array',
items: {
type: 'boolean',
nullable: true
}
}
}
}
const options = {
largeArraySize: 2e4,
largeArrayMechanism: 'default'
}
const stringify = build(schema, options)
const data = { ids: new Array(2e4).fill(null) }
const result = stringify(data)
t.same(result, JSON.stringify(data))
t.same(JSON.parse(result), data)
})

@@ -107,7 +107,3 @@ 'use strict'

const stringify = build(schema)
const value = stringify({
str: 1
})
t.equal(value, '{"str":null}')
t.throws(() => stringify({ str: 1 }))
})

@@ -407,3 +403,3 @@

test('one array item match oneOf types', (t) => {
t.plan(1)
t.plan(3)

@@ -434,11 +430,9 @@ const schema = {

const responseWithMappedType = stringify({
data: [false, 'foo']
})
t.equal('{"data":["foo"]}', responseWithMappedType)
t.equal(stringify({ data: ['foo'] }), '{"data":["foo"]}')
t.equal(stringify({ data: [1] }), '{"data":[1]}')
t.throws(() => stringify({ data: [false, 'foo'] }))
})
test('some array items match oneOf types', (t) => {
t.plan(1)
t.plan(2)

@@ -469,7 +463,4 @@ const schema = {

const responseWithMappedTypes = stringify({
data: [false, 'foo', true, 5]
})
t.equal('{"data":["foo",5]}', responseWithMappedTypes)
t.equal(stringify({ data: ['foo', 5] }), '{"data":["foo",5]}')
t.throws(() => stringify({ data: [false, 'foo', true, 5] }))
})

@@ -504,7 +495,3 @@

const emptyResponse = stringify({
data: [null, false, true, undefined, [], {}]
})
t.equal('{"data":[]}', emptyResponse)
t.throws(() => stringify({ data: [null, false, true, undefined, [], {}] }))
})

@@ -66,3 +66,3 @@ 'use strict'

test('patternProperties - number coerce', (t) => {
t.plan(1)
t.plan(2)
const stringify = build({

@@ -79,4 +79,12 @@ title: 'check number coerce',

const obj = { foo: true, ofoo: '42', xfoo: 'string', arrfoo: [1, 2], objfoo: { num: 42 } }
t.equal(stringify(obj), '{"foo":1,"ofoo":42,"xfoo":null,"arrfoo":null,"objfoo":null}')
const coercibleValues = { foo: true, ofoo: '42' }
t.equal(stringify(coercibleValues), '{"foo":1,"ofoo":42}')
const incoercibleValues = { xfoo: 'string', arrfoo: [1, 2], objfoo: { num: 42 } }
try {
stringify(incoercibleValues)
t.fail('should throw an error')
} catch (err) {
t.ok(err)
}
})

@@ -124,3 +132,3 @@

test('patternProperties - array coerce', (t) => {
t.plan(1)
t.plan(2)
const stringify = build({

@@ -140,4 +148,7 @@ title: 'check array coerce',

const obj = { foo: 'true', ofoo: 0, arrfoo: [1, 2], objfoo: { tyrion: 'lannister' } }
t.equal(stringify(obj), '{"foo":["t","r","u","e"],"ofoo":[],"arrfoo":["1","2"],"objfoo":[]}')
const coercibleValues = { arrfoo: [1, 2] }
t.equal(stringify(coercibleValues), '{"arrfoo":["1","2"]}')
const incoercibleValues = { foo: 'true', ofoo: 0, objfoo: { tyrion: 'lannister' } }
t.throws(() => stringify(incoercibleValues))
})

@@ -235,5 +235,5 @@ 'use strict'

} catch (e) {
t.equal(e.message, '"num" is required!')
t.equal(e.message, 'The value "aaa" cannot be converted to an integer.')
t.pass()
}
})

@@ -6,13 +6,11 @@ 'use strict'

const stringify = build({
$defs: {
type: 'foooo"bar'
},
patternProperties: {
x: { $ref: '#/$defs' }
}
})
t.throws(() => {
stringify({ x: 0 })
}, 'Cannot coerce 0 to "foo"bar"')
build({
$defs: {
type: 'foooo"bar'
},
patternProperties: {
x: { $ref: '#/$defs' }
}
})
}, 'foooo"bar unsupported')
'use strict'
const test = require('tap').test
const moment = require('moment')
const build = require('..')

@@ -91,2 +90,23 @@

test('possibly nullable number primitive alternative with null value', (t) => {
t.plan(1)
const schema = {
title: 'simple object with multi-type nullable primitive',
type: 'object',
properties: {
data: {
type: ['boolean']
}
}
}
const stringify = build(schema)
const value = stringify({
data: null
})
t.equal(value, '{"data":false}')
})
test('nullable integer primitive', (t) => {

@@ -365,3 +385,3 @@ t.plan(1)

date: new Date('2018-04-20T07:52:31.017Z'),
dateObject: moment('2018-04-21T07:52:31.017Z')
dateObject: new Date('2018-04-21T07:52:31.017Z')
})

@@ -438,3 +458,20 @@ t.equal(value, '{"date":"2018-04-20T07:52:31.017Z","dateObject":"2018-04-21T07:52:31.017Z"}')

const stringify = build(schema)
t.throws(() => stringify({ arr: null }), new TypeError('Property \'arr\' should be of type array, received \'null\' instead.'))
t.throws(() => stringify({ arr: null }), new TypeError('The value \'null\' does not match schema definition.'))
})
test('throw an error if none of types matches', (t) => {
t.plan(1)
const schema = {
title: 'simple object with multi-type nullable primitive',
type: 'object',
properties: {
data: {
type: ['number', 'boolean']
}
}
}
const stringify = build(schema)
t.throws(() => stringify({ data: 'string' }), 'The value "string" does not match schema definition.')
})

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc