fast-json-stringify
Advanced tools
Comparing version 3.2.0 to 4.0.0
@@ -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' }], |
1072
index.js
@@ -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
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
295334
69
10878
15
685
5
+ Addedfast-uri@2.3.0(transitive)
- Removedfast-uri@1.0.1(transitive)
Updatedfast-uri@^2.0.0