Socket
Socket
Sign inDemoInstall

fast-json-stringify

Package Overview
Dependencies
10
Maintainers
8
Versions
158
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 5.5.0 to 5.6.0

test/multi-type-serializer.test.js

21

benchmark/bench.js

@@ -218,2 +218,23 @@ 'use strict'

{
name: 'simple object',
schema: {
title: 'Example Schema',
type: 'object',
properties: {
firstName: {
type: 'string'
},
lastName: {
type: ['string', 'null']
},
age: {
description: 'Age in years',
type: 'integer',
minimum: 0
}
}
},
input: { firstName: 'Max', lastName: 'Power', age: 22 }
},
{
name: 'object with const string property',

@@ -220,0 +241,0 @@ schema: {

253

index.js

@@ -17,2 +17,10 @@ 'use strict'

let largeArrayMechanism = 'default'
const validRoundingMethods = [
'floor',
'ceil',
'round',
'trunc'
]
const validLargeArrayMechanisms = [

@@ -23,9 +31,3 @@ 'default',

const addComma = `
if (addComma) {
json += ','
} else {
addComma = true
}
`
const addComma = '!addComma && (addComma = true) || (json += \',\')'

@@ -46,3 +48,3 @@ function isValidSchema (schema, name) {

function resolveRef (location, ref) {
function resolveRef (context, location, ref) {
let hashIndex = ref.indexOf('#')

@@ -56,3 +58,3 @@ if (hashIndex === -1) {

const schema = refResolver.getSchema(schemaId, jsonPointer)
const schema = context.refResolver.getSchema(schemaId, jsonPointer)

@@ -65,3 +67,3 @@ if (schema === undefined) {

if (schema.$ref !== undefined) {
return resolveRef(newLocation, schema.$ref)
return resolveRef(context, newLocation, schema.$ref)
}

@@ -72,27 +74,23 @@

const contextFunctionsNamesBySchema = new Map()
let rootSchemaId = null
let refResolver = null
let contextFunctions = null
let validatorSchemasIds = null
function build (schema, options) {
contextFunctionsNamesBySchema.clear()
isValidSchema(schema)
contextFunctions = []
validatorSchemasIds = new Set()
options = options || {}
refResolver = new RefResolver()
const context = {
functions: [],
functionsCounter: 0,
functionsNamesBySchema: new Map(),
options,
refResolver: new RefResolver(),
rootSchemaId: schema.$id || randomUUID(),
validatorSchemasIds: new Set()
}
rootSchemaId = schema.$id || randomUUID()
context.refResolver.addSchema(schema, context.rootSchemaId)
isValidSchema(schema)
refResolver.addSchema(schema, rootSchemaId)
if (options.schema) {
for (const key of Object.keys(options.schema)) {
isValidSchema(options.schema[key], key)
refResolver.addSchema(options.schema[key], key)
context.refResolver.addSchema(options.schema[key], key)
}

@@ -102,3 +100,3 @@ }

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

@@ -117,13 +115,31 @@ }

if (options.largeArraySize) {
if (!Number.isNaN(Number.parseInt(options.largeArraySize, 10))) {
if (typeof options.largeArraySize === 'string' && Number.isFinite(Number.parseInt(options.largeArraySize, 10))) {
largeArraySize = Number.parseInt(options.largeArraySize, 10)
} else if (typeof options.largeArraySize === 'number' && Number.isInteger(options.largeArraySize)) {
largeArraySize = options.largeArraySize
} else if (typeof options.largeArraySize === 'bigint') {
largeArraySize = Number(options.largeArraySize)
} else {
throw new Error(`Unsupported large array size. Expected integer-like, got ${options.largeArraySize}`)
throw new Error(`Unsupported large array size. Expected integer-like, got ${typeof options.largeArraySize} with value ${options.largeArraySize}`)
}
}
const location = new Location(schema, rootSchemaId)
const code = buildValue(location, 'input')
const location = new Location(schema, context.rootSchemaId)
const code = buildValue(context, location, 'input')
const contextFunctionCode = `
let contextFunctionCode
// If we have only the invocation of the 'anonymous0' function, we would
// basically just wrap the 'anonymous0' function in the 'main' function and
// and the overhead of the intermediate variabe 'json'. We can avoid the
// wrapping and the unnecessary memory allocation by aliasing 'anonymous0' to
// 'main'
if (code === 'json += anonymous0(input)') {
contextFunctionCode = `
${context.functions.join('\n')}
const main = anonymous0
return main
`
} else {
contextFunctionCode = `
function main (input) {

@@ -134,5 +150,6 @@ let json = ''

}
${contextFunctions.join('\n')}
${context.functions.join('\n')}
return main
`
`
}

@@ -142,7 +159,7 @@ const serializer = new Serializer(options)

for (const schemaId of validatorSchemasIds) {
const schema = refResolver.getSchema(schemaId)
for (const schemaId of context.validatorSchemasIds) {
const schema = context.refResolver.getSchema(schemaId)
validator.addSchema(schema, schemaId)
const dependencies = refResolver.getSchemaDependencies(schemaId)
const dependencies = context.refResolver.getSchemaDependencies(schemaId)
for (const [schemaId, schema] of Object.entries(dependencies)) {

@@ -153,4 +170,2 @@ validator.addSchema(schema, schemaId)

const dependenciesName = ['validator', 'serializer', contextFunctionCode]
if (options.debugMode) {

@@ -164,3 +179,3 @@ options.mode = 'debug'

serializer,
code: dependenciesName.join('\n'),
code: `validator\nserializer\n${contextFunctionCode}`,
ajv: validator.ajv

@@ -172,3 +187,3 @@ }

// lazy load
const isValidatorUsed = validatorSchemasIds.size > 0
const isValidatorUsed = context.validatorSchemasIds.size > 0
const buildStandaloneCode = require('./lib/standalone')

@@ -182,8 +197,2 @@ return buildStandaloneCode(options, validator, isValidatorUsed, contextFunctionCode)

refResolver = null
rootSchemaId = null
contextFunctions = null
validatorSchemasIds = null
contextFunctionsNamesBySchema.clear()
return stringifyFunc

@@ -193,8 +202,8 @@ }

const objectKeywords = [
'properties',
'required',
'additionalProperties',
'patternProperties',
'maxProperties',
'minProperties',
'required',
'properties',
'patternProperties',
'additionalProperties',
'dependencies'

@@ -250,3 +259,3 @@ ]

function buildExtraObjectPropertiesSerializer (location) {
function buildExtraObjectPropertiesSerializer (context, location) {
const schema = location.schema

@@ -273,9 +282,2 @@ const propertiesKeys = Object.keys(schema.properties || {})

try {
RegExp(propertyKey)
} catch (err) {
const jsonPointer = propertyLocation.getSchemaRef()
throw new Error(`${err.message}. Invalid pattern property regexp key ${propertyKey} at ${jsonPointer}`)
}
code += `

@@ -285,3 +287,3 @@ if (/${propertyKey.replace(/\\*\//g, '\\/')}/.test(key)) {

json += serializer.asString(key) + ':'
${buildValue(propertyLocation, 'value')}
${buildValue(context, propertyLocation, 'value')}
continue

@@ -307,3 +309,3 @@ }

json += serializer.asString(key) + ':'
${buildValue(propertyLocation, 'value')}
${buildValue(context, propertyLocation, 'value')}
`

@@ -319,3 +321,3 @@ }

function buildInnerObject (location) {
function buildInnerObject (context, location) {
const schema = location.schema

@@ -330,7 +332,6 @@ const required = schema.required || []

if (propertyLocation.schema.$ref) {
propertyLocation = resolveRef(location, propertyLocation.schema.$ref)
propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref)
}
const sanitized = JSON.stringify(key)
const asString = JSON.stringify(sanitized)

@@ -343,6 +344,6 @@ // Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,

${addComma}
json += ${asString} + ':'
json += ${JSON.stringify(sanitized + ':')}
`
code += buildValue(propertyLocation, `obj[${JSON.stringify(key)}]`)
code += buildValue(context, propertyLocation, `obj[${sanitized}]`)

@@ -354,3 +355,3 @@ const defaultValue = propertyLocation.schema.default

${addComma}
json += ${asString} + ':' + ${JSON.stringify(JSON.stringify(defaultValue))}
json += ${JSON.stringify(sanitized + ':' + JSON.stringify(defaultValue))}
`

@@ -375,3 +376,3 @@ } else if (required.includes(key)) {

if (schema.patternProperties || schema.additionalProperties) {
code += buildExtraObjectPropertiesSerializer(location)
code += buildExtraObjectPropertiesSerializer(context, location)
}

@@ -382,3 +383,3 @@

function mergeAllOfSchema (location, schema, mergedSchema) {
function mergeAllOfSchema (context, location, schema, mergedSchema) {
const allOfLocation = location.getPropertyLocation('allOf')

@@ -391,3 +392,3 @@

const allOfSchemaLocation = allOfLocation.getPropertyLocation(i)
allOfSchema = resolveRef(allOfSchemaLocation, allOfSchema.$ref).schema
allOfSchema = resolveRef(context, allOfSchemaLocation, allOfSchema.$ref).schema
}

@@ -473,3 +474,3 @@

if (allOfSchema.allOf !== undefined) {
mergeAllOfSchema(location, allOfSchema, mergedSchema)
mergeAllOfSchema(context, location, allOfSchema, mergedSchema)
}

@@ -480,8 +481,8 @@ }

mergedSchema.$id = `merged_${randomUUID()}`
refResolver.addSchema(mergedSchema)
context.refResolver.addSchema(mergedSchema)
location.addMergedSchema(mergedSchema, mergedSchema.$id)
}
function addIfThenElse (location, input) {
validatorSchemasIds.add(location.getSchemaId())
function addIfThenElse (context, location, input) {
context.validatorSchemasIds.add(location.getSchemaId())

@@ -507,5 +508,5 @@ const schema = merge({}, location.schema)

if (validator.validate("${ifSchemaRef}", ${input})) {
${buildValue(thenLocation, input)}
${buildValue(context, thenLocation, input)}
} else {
${buildValue(elseLocation, input)}
${buildValue(context, elseLocation, input)}
}

@@ -522,15 +523,15 @@ `

function buildObject (location) {
function buildObject (context, location) {
const schema = location.schema
if (contextFunctionsNamesBySchema.has(schema)) {
return contextFunctionsNamesBySchema.get(schema)
if (context.functionsNamesBySchema.has(schema)) {
return context.functionsNamesBySchema.get(schema)
}
const functionName = generateFuncName()
contextFunctionsNamesBySchema.set(schema, functionName)
const functionName = generateFuncName(context)
context.functionsNamesBySchema.set(schema, functionName)
let schemaRef = location.getSchemaRef()
if (schemaRef.startsWith(rootSchemaId)) {
schemaRef = schemaRef.replace(rootSchemaId, '')
if (schemaRef.startsWith(context.rootSchemaId)) {
schemaRef = schemaRef.replace(context.rootSchemaId, '')
}

@@ -544,19 +545,18 @@

functionCode += `
var obj = ${toJSON('input')}
var json = '{'
var addComma = false
const obj = ${toJSON('input')}
let json = '{'
let addComma = false
`
functionCode += buildInnerObject(location)
functionCode += buildInnerObject(context, location)
functionCode += `
json += '}'
return json
return json + '}'
}
`
contextFunctions.push(functionCode)
context.functions.push(functionCode)
return functionName
}
function buildArray (location) {
function buildArray (context, location) {
const schema = location.schema

@@ -568,3 +568,3 @@

if (itemsLocation.schema.$ref) {
itemsLocation = resolveRef(itemsLocation, itemsLocation.schema.$ref)
itemsLocation = resolveRef(context, itemsLocation, itemsLocation.schema.$ref)
}

@@ -574,12 +574,12 @@

if (contextFunctionsNamesBySchema.has(schema)) {
return contextFunctionsNamesBySchema.get(schema)
if (context.functionsNamesBySchema.has(schema)) {
return context.functionsNamesBySchema.get(schema)
}
const functionName = generateFuncName()
contextFunctionsNamesBySchema.set(schema, functionName)
const functionName = generateFuncName(context)
context.functionsNamesBySchema.set(schema, functionName)
let schemaRef = location.getSchemaRef()
if (schemaRef.startsWith(rootSchemaId)) {
schemaRef = schemaRef.replace(rootSchemaId, '')
if (schemaRef.startsWith(context.rootSchemaId)) {
schemaRef = schemaRef.replace(context.rootSchemaId, '')
}

@@ -594,3 +594,3 @@

if (!Array.isArray(obj)) {
throw new TypeError(\`The value '$\{obj}' does not match schema definition.\`)
throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`)
}

@@ -600,3 +600,3 @@ const arrayLength = obj.length

if (!schema.additionalItems) {
if (!schema.additionalItems && Array.isArray(itemsSchema)) {
functionCode += `

@@ -624,3 +624,3 @@ if (arrayLength > ${itemsSchema.length}) {

const item = itemsSchema[i]
const tmpRes = buildValue(itemsLocation.getPropertyLocation(i), `obj[${i}]`)
const tmpRes = buildValue(context, itemsLocation.getPropertyLocation(i), `obj[${i}]`)
functionCode += `

@@ -645,4 +645,3 @@ if (${i} < arrayLength) {

for (let i = ${itemsSchema.length}; i < arrayLength; i++) {
let json = JSON.stringify(obj[i])
jsonOutput += json
jsonOutput += JSON.stringify(obj[i])
if (i < arrayLength - 1) {

@@ -654,3 +653,3 @@ jsonOutput += ','

} else {
const code = buildValue(itemsLocation, 'obj[i]')
const code = buildValue(context, itemsLocation, 'obj[i]')
functionCode += `

@@ -671,3 +670,3 @@ for (let i = 0; i < arrayLength; i++) {

contextFunctions.push(functionCode)
context.functions.push(functionCode)
return functionName

@@ -713,8 +712,7 @@ }

let genFuncNameCounter = 0
function generateFuncName () {
return 'anonymous' + genFuncNameCounter++
function generateFuncName (context) {
return 'anonymous' + context.functionsCounter++
}
function buildMultiTypeSerializer (location, input) {
function buildMultiTypeSerializer (context, location, input) {
const schema = location.schema

@@ -727,3 +725,3 @@ const types = schema.type.sort(t1 => t1 === 'null' ? -1 : 1)

location.schema = { ...location.schema, type }
const nestedResult = buildSingleTypeSerializer(location, input)
const nestedResult = buildSingleTypeSerializer(context, location, input)

@@ -779,4 +777,8 @@ const statement = index === 0 ? 'if' : 'else if'

})
let schemaRef = location.getSchemaRef()
if (schemaRef.startsWith(context.rootSchemaId)) {
schemaRef = schemaRef.replace(context.rootSchemaId, '')
}
code += `
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
else throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`)
`

@@ -787,3 +789,3 @@

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

@@ -812,7 +814,7 @@

case 'object': {
const funcName = buildObject(location)
const funcName = buildObject(context, location)
return `json += ${funcName}(${input})`
}
case 'array': {
const funcName = buildArray(location)
const funcName = buildArray(context, location)
return `json += ${funcName}(${input})`

@@ -854,3 +856,3 @@ }

function buildValue (location, input) {
function buildValue (context, location, input) {
let schema = location.schema

@@ -863,3 +865,3 @@

if (schema.$ref) {
location = resolveRef(location, schema.$ref)
location = resolveRef(context, location, schema.$ref)
schema = location.schema

@@ -876,7 +878,7 @@ }

if (schema.if && schema.then) {
return addIfThenElse(location, input)
return addIfThenElse(context, location, input)
}
if (schema.allOf) {
mergeAllOfSchema(location, schema, clone(schema))
mergeAllOfSchema(context, location, schema, clone(schema))
schema = location.schema

@@ -890,3 +892,3 @@ }

if (type === undefined && (schema.anyOf || schema.oneOf)) {
validatorSchemasIds.add(location.getSchemaId())
context.validatorSchemasIds.add(location.getSchemaId())

@@ -899,3 +901,3 @@ const type = schema.anyOf ? 'anyOf' : 'oneOf'

const schemaRef = optionLocation.getSchemaRef()
const nestedResult = buildValue(optionLocation, input)
const nestedResult = buildValue(context, optionLocation, input)
code += `

@@ -907,4 +909,9 @@ ${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))

let schemaRef = location.getSchemaRef()
if (schemaRef.startsWith(context.rootSchemaId)) {
schemaRef = schemaRef.replace(context.rootSchemaId, '')
}
code += `
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
else throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`)
`

@@ -926,5 +933,5 @@ return code

} else if (Array.isArray(type)) {
code += buildMultiTypeSerializer(location, input)
code += buildMultiTypeSerializer(context, location, input)
} else {
code += buildSingleTypeSerializer(location, input)
code += buildSingleTypeSerializer(context, location, input)
}

@@ -931,0 +938,0 @@

@@ -7,4 +7,4 @@ 'use strict'

module.exports = class Serializer {
constructor (options = {}) {
switch (options.rounding) {
constructor (options) {
switch (options && options.rounding) {
case 'floor':

@@ -19,2 +19,3 @@ this.parseInteger = Math.floor

break
case 'trunc':
default:

@@ -27,13 +28,24 @@ this.parseInteger = Math.trunc

asInteger (i) {
if (typeof i === 'bigint') {
if (typeof i === 'number') {
if (i === Infinity || i === -Infinity) {
throw new Error(`The value "${i}" cannot be converted to an integer.`)
}
if ((i | 0) === i) {
return '' + i
}
if (Number.isNaN(i)) {
throw new Error(`The value "${i}" cannot be converted to an integer.`)
}
return this.parseInteger(i)
} else if (i === null) {
return '0'
} else 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) || !Number.isFinite(integer)) {
if (Number.isFinite(integer)) {
return '' + integer
} else {
throw new Error(`The value "${i}" cannot be converted to an integer.`)
} else {
return '' + integer
}

@@ -44,10 +56,13 @@ }

asNumber (i) {
const num = Number(i)
if (Number.isNaN(num)) {
if (typeof i !== 'number') {
i = Number(i)
}
// NaN !== NaN
if (i !== i) { // eslint-disable-line no-self-compare
throw new Error(`The value "${i}" cannot be converted to a number.`)
} else if (!Number.isFinite(num)) {
return null
} else {
return '' + num
}
if (i === Infinity || i === -Infinity) {
return 'null'
}
return '' + i
}

@@ -93,11 +108,14 @@

asString (str) {
const quotes = '"'
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 (typeof str !== 'string') {
if (str === null) {
return '""'
}
if (str instanceof Date) {
return '"' + str.toISOString() + '"'
}
if (str instanceof RegExp) {
str = str.source
} else {
str = str.toString()
}
}

@@ -107,6 +125,4 @@

if (!STR_ESCAPE.test(str)) {
return quotes + str + quotes
}
if (str.length < 42) {
return '"' + str + '"'
} else if (str.length < 42) {
return this.asStringSmall(str)

@@ -113,0 +129,0 @@ } else {

@@ -28,9 +28,9 @@ const fs = require('fs')

}
const serializerOptions = options && options.rounding ? JSON.stringify({ options: options.rounding }) : ''
return `
'use strict'
${serializerCode.replace("'use strict'", '').replace('module.exports = ', '')}
${buildAjvCode}
const serializer = new Serializer(${JSON.stringify(options || {})})
const serializer = new Serializer(${serializerOptions})
${ajvSchemasCode}

@@ -40,6 +40,5 @@

module.exports = main
`
module.exports = main`
}
module.exports = buildStandaloneCode
{
"name": "fast-json-stringify",
"version": "5.5.0",
"version": "5.6.0",
"description": "Stringify your JSON at max speed",

@@ -15,3 +15,3 @@ "main": "index.js",

"test:typescript": "tsd",
"test:unit": "tap -J test/*.test.js test/**/*.test.js",
"test:unit": "tap",
"test": "npm run test:unit && npm run test:typescript"

@@ -43,9 +43,6 @@ },

"is-my-json-valid": "^2.20.0",
"luxon": "^3.0.1",
"proxyquire": "^2.1.3",
"semver": "^7.1.0",
"simple-git": "^3.7.1",
"standard": "^17.0.0",
"tap": "^16.0.1",
"tsd": "^0.24.1",
"tsd": "^0.25.0",
"webpack": "^5.40.0"

@@ -52,0 +49,0 @@ },

@@ -551,3 +551,3 @@ # fast-json-stringify

The `type: integer` property will be truncated if a floating point is provided.
You can customize this behaviour with the `rounding` option that will accept [`round`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round), [`ceil`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil) or [`floor`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor):
You can customize this behaviour with the `rounding` option that will accept [`round`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round), [`ceil`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil), [`floor`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor) or [`trunc`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc). Default is `trunc`:

@@ -554,0 +554,0 @@ ```js

'use strict'
const test = require('tap').test
const { DateTime } = require('luxon')
const build = require('..')
process.env.TZ = 'UTC'
test('allOf: combine type and format ', (t) => {

@@ -17,5 +18,5 @@ t.plan(1)

const stringify = build(schema)
const date = new Date()
const date = new Date(1674263005800)
const value = stringify(date)
t.equal(value, `"${DateTime.fromJSDate(date).toFormat('HH:mm:ss')}"`)
t.equal(value, '"01:03:25"')
})

@@ -486,1 +487,49 @@

})
test('allOf: throw Error if types mismatch ', (t) => {
t.plan(1)
const schema = {
allOf: [
{ type: 'string' },
{ type: 'number' }
]
}
t.throws(() => build(schema), new Error('allOf schemas have different type values'))
})
test('allOf: throw Error if format mismatch ', (t) => {
t.plan(1)
const schema = {
allOf: [
{ format: 'date' },
{ format: 'time' }
]
}
t.throws(() => build(schema), new Error('allOf schemas have different format values'))
})
test('allOf: throw Error if nullable mismatch /1', (t) => {
t.plan(1)
const schema = {
allOf: [
{ nullable: true },
{ nullable: false }
]
}
t.throws(() => build(schema), new Error('allOf schemas have different nullable values'))
})
test('allOf: throw Error if nullable mismatch /2', (t) => {
t.plan(1)
const schema = {
allOf: [
{ nullable: false },
{ nullable: true }
]
}
t.throws(() => build(schema), new Error('allOf schemas have different nullable values'))
})

@@ -155,1 +155,78 @@ 'use strict'

})
test('should throw a TypeError with the path to the key of the invalid value /1', (t) => {
t.plan(1)
// any on Foo codepath.
const schema = {
anyOf: [
{
type: 'object',
properties: {
kind: {
type: 'string',
enum: ['Foo']
},
value: {}
}
},
{
type: 'object',
properties: {
kind: {
type: 'string',
enum: ['Bar']
},
value: {
type: 'number'
}
}
}
]
}
const stringify = build(schema)
t.throws(() => stringify({ kind: 'Baz', value: 1 }), new TypeError('The value of \'#\' does not match schema definition.'))
})
test('should throw a TypeError with the path to the key of the invalid value /2', (t) => {
t.plan(1)
// any on Foo codepath.
const schema = {
type: 'object',
properties: {
data: {
anyOf: [
{
type: 'object',
properties: {
kind: {
type: 'string',
enum: ['Foo']
},
value: {}
}
},
{
type: 'object',
properties: {
kind: {
type: 'string',
enum: ['Bar']
},
value: {
type: 'number'
}
}
}
]
}
}
}
const stringify = build(schema)
t.throws(() => stringify({ data: { kind: 'Baz', value: 1 } }), new TypeError('The value of \'#/properties/data\' does not match schema definition.'))
})
'use strict'
const { DateTime } = require('luxon')
const { test } = require('tap')
const build = require('..')
process.env.TZ = 'UTC'
test('object with multiple types field', (t) => {

@@ -522,6 +523,6 @@ t.plan(2)

const data = {
prop: { nestedProp: new Date() }
prop: { nestedProp: new Date(1674263005800) }
}
t.equal(withOneOfStringify(data), `{"prop":{"nestedProp":"${DateTime.fromJSDate(data.prop.nestedProp).toISODate()}"}}`)
t.equal(withOneOfStringify(data), '{"prop":{"nestedProp":"2023-01-21"}}')
})

@@ -551,6 +552,5 @@

const data = {
prop: { nestedProp: new Date() }
prop: { nestedProp: new Date(1674263005800) }
}
t.equal(withOneOfStringify(data), `{"prop":{"nestedProp":"${DateTime.fromJSDate(data.prop.nestedProp).toFormat('HH:mm:ss')}"}}`)
t.equal(withOneOfStringify(data), '{"prop":{"nestedProp":"01:03:25"}}')
})

@@ -557,0 +557,0 @@

@@ -6,2 +6,3 @@ 'use strict'

const build = require('..')
const Ajv = require('ajv')

@@ -269,3 +270,3 @@ test('error on invalid largeArrayMechanism', (t) => {

test('array items is a list of schema and additionalItems is false', (t) => {
test('array items is a list of schema and additionalItems is false /1', (t) => {
t.plan(1)

@@ -279,5 +280,3 @@

items: [
{
type: 'string'
}
{ type: 'string' }
],

@@ -290,16 +289,53 @@ additionalItems: false

const stringify = build(schema)
t.throws(() => stringify({ foo: ['foo', 'bar'] }), new Error('Item at 1 does not match schema definition.'))
})
try {
stringify({
foo: [
'foo',
'bar'
]
})
t.fail()
} catch (error) {
t.ok(/does not match schema definition./.test(error.message))
test('array items is a list of schema and additionalItems is false /2', (t) => {
t.plan(3)
const schema = {
type: 'object',
properties: {
foo: {
type: 'array',
items: [
{ type: 'string' },
{ type: 'string' }
],
additionalItems: false
}
}
}
const stringify = build(schema)
t.throws(() => stringify({ foo: [1, 'bar'] }), new Error('Item at 0 does not match schema definition.'))
t.throws(() => stringify({ foo: ['foo', 1] }), new Error('Item at 1 does not match schema definition.'))
t.throws(() => stringify({ foo: ['foo', 'bar', 'baz'] }), new Error('Item at 2 does not match schema definition.'))
})
test('array items is a schema and additionalItems is false', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
foo: {
type: 'array',
items: { type: 'string' },
additionalItems: false
}
}
}
const stringify = build(schema)
// ajv ignores additionalItems if items is not an Array
const ajv = new Ajv({ allErrors: true, strict: false })
const validate = ajv.compile(schema)
t.same(stringify({ foo: ['foo', 'bar'] }), '{"foo":["foo","bar"]}')
t.equal(validate({ foo: ['foo', 'bar'] }), true)
})
// https://github.com/fastify/fast-json-stringify/issues/279

@@ -500,1 +536,84 @@ test('object array with anyOf and symbol', (t) => {

})
test('error on invalid value for largeArraySize /1', (t) => {
t.plan(1)
t.throws(() => build({
title: 'large array of null values with default mechanism',
type: 'object',
properties: {
ids: {
type: 'array',
items: { type: 'null' }
}
}
}, {
largeArraySize: 'invalid'
}), Error('Unsupported large array size. Expected integer-like, got string with value invalid'))
})
test('error on invalid value for largeArraySize /2', (t) => {
t.plan(1)
t.throws(() => build({
title: 'large array of null values with default mechanism',
type: 'object',
properties: {
ids: {
type: 'array',
items: { type: 'null' }
}
}
}, {
largeArraySize: Infinity
}), Error('Unsupported large array size. Expected integer-like, got number with value Infinity'))
})
test('error on invalid value for largeArraySize /3', (t) => {
t.plan(1)
t.throws(() => build({
title: 'large array of null values with default mechanism',
type: 'object',
properties: {
ids: {
type: 'array',
items: { type: 'null' }
}
}
}, {
largeArraySize: [200]
}), Error('Unsupported large array size. Expected integer-like, got object with value 200'))
})
buildTest({
title: 'large array of integers with largeArraySize is bigint',
type: 'object',
properties: {
ids: {
type: 'array',
items: { type: 'integer' }
}
}
}, {
ids: new Array(2e4).fill(42)
}, {
largeArraySize: 20000n,
largeArrayMechanism: 'default'
})
buildTest({
title: 'large array of integers with largeArraySize is valid string',
type: 'object',
properties: {
ids: {
type: 'array',
items: { type: 'integer' }
}
}
}, {
ids: new Array(1e4).fill(42)
}, {
largeArraySize: '10000',
largeArrayMechanism: 'default'
})
'use strict'
const test = require('tap').test
const { DateTime } = require('luxon')
const validator = require('is-my-json-valid')
const build = require('..')
process.env.TZ = 'UTC'
test('render a date in a string as JSON', (t) => {

@@ -15,3 +16,3 @@ t.plan(2)

}
const toStringify = new Date()
const toStringify = new Date(1674263005800)

@@ -34,3 +35,3 @@ const validate = validator(schema)

}
const toStringify = new Date()
const toStringify = new Date(1674263005800)

@@ -54,3 +55,3 @@ const validate = validator(schema)

}
const toStringify = new Date()
const toStringify = new Date(1674263005800)

@@ -73,3 +74,3 @@ const validate = validator(schema)

}
const toStringify = new Date()
const toStringify = new Date(1674263005800)

@@ -80,3 +81,3 @@ const validate = validator(schema)

t.equal(output, `"${DateTime.fromJSDate(toStringify).toISODate()}"`)
t.equal(output, '"2023-01-21"')
t.ok(validate(JSON.parse(output)), 'valid schema')

@@ -94,3 +95,3 @@ })

}
const toStringify = new Date()
const toStringify = new Date(1674263005800)

@@ -101,3 +102,3 @@ const validate = validator(schema)

t.equal(output, `"${DateTime.fromJSDate(toStringify).toISODate()}"`)
t.equal(output, '"2023-01-21"')
t.ok(validate(JSON.parse(output)), 'valid schema')

@@ -120,3 +121,3 @@ })

t.equal(output, `"${DateTime.fromJSDate(toStringify).toISODate()}"`)
t.equal(output, '"2020-01-01"')
t.ok(validate(JSON.parse(output)), 'valid schema')

@@ -133,3 +134,3 @@ })

}
const toStringify = new Date()
const toStringify = new Date(1674263005800)

@@ -143,3 +144,3 @@ const validate = validator(schema)

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

@@ -157,3 +158,3 @@ })

}
const toStringify = new Date()
const toStringify = new Date(1674263005800)

@@ -167,3 +168,3 @@ const validate = validator(schema)

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

@@ -180,3 +181,3 @@ })

}
const midnight = new Date(new Date().setHours(24))
const midnight = new Date(new Date(1674263005800).setHours(24))

@@ -190,3 +191,3 @@ const validate = validator(schema)

t.equal(output, `"${DateTime.fromJSDate(midnight).toFormat('HH:mm:ss')}"`)
t.equal(output, '"00:03:25"')
t.ok(validate(JSON.parse(output)), 'valid schema')

@@ -212,3 +213,3 @@ })

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

@@ -230,3 +231,3 @@ })

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

@@ -403,7 +404,7 @@ const validate = validator(schema)

const date = new Date()
const date = new Date(1674263005800)
const input = { updatedAt: date }
const { output } = serialize(schema, input)
t.equal(output, JSON.stringify({ updatedAt: DateTime.fromJSDate(date).toFormat('HH:mm:ss') }))
t.equal(output, JSON.stringify({ updatedAt: '01:03:25' }))
})

@@ -423,6 +424,6 @@

const date = new Date()
const date = new Date(1674263005800)
const { output } = serialize(schema, date)
t.equal(output, `"${DateTime.fromJSDate(date).toFormat('HH:mm:ss')}"`)
t.equal(output, '"01:03:25"')
})

@@ -518,3 +519,3 @@

}
const toStringify = new Date()
const toStringify = new Date(1674263005800)

@@ -542,6 +543,6 @@ const stringify = build(schema)

const data = new Date()
const data = new Date(1674263005800)
const result = stringify(data)
t.same(result, `"${DateTime.fromJSDate(data).toISODate()}"`)
t.same(result, '"2023-01-21"')
})

@@ -567,1 +568,93 @@

})
test('should serialize also an invalid string value, even if it is not a valid date', (t) => {
t.plan(2)
const schema = {
title: 'a date in a string',
type: 'string',
format: 'date-time',
nullable: true
}
const toStringify = 'invalid'
const validate = validator(schema)
const stringify = build(schema)
const output = stringify(toStringify)
t.equal(output, JSON.stringify(toStringify))
t.not(validate(JSON.parse(output)), 'valid schema')
})
test('should throw an error if value can not be transformed to date-time', (t) => {
t.plan(2)
const schema = {
title: 'a date in a string',
type: 'string',
format: 'date-time',
nullable: true
}
const toStringify = true
const validate = validator(schema)
const stringify = build(schema)
t.throws(() => stringify(toStringify), new Error('The value "true" cannot be converted to a date-time.'))
t.not(validate(toStringify))
})
test('should throw an error if value can not be transformed to date', (t) => {
t.plan(2)
const schema = {
title: 'a date in a string',
type: 'string',
format: 'date',
nullable: true
}
const toStringify = true
const validate = validator(schema)
const stringify = build(schema)
t.throws(() => stringify(toStringify), new Error('The value "true" cannot be converted to a date.'))
t.not(validate(toStringify))
})
test('should throw an error if value can not be transformed to time', (t) => {
t.plan(2)
const schema = {
title: 'a time in a string',
type: 'string',
format: 'time',
nullable: true
}
const toStringify = true
const validate = validator(schema)
const stringify = build(schema)
t.throws(() => stringify(toStringify), new Error('The value "true" cannot be converted to a time.'))
t.not(validate(toStringify))
})
test('should serialize also an invalid string value, even if it is not a valid time', (t) => {
t.plan(2)
const schema = {
title: 'a time in a string',
type: 'string',
format: 'time',
nullable: true
}
const toStringify = 'invalid'
const validate = validator(schema)
const stringify = build(schema)
const output = stringify(toStringify)
t.equal(output, JSON.stringify(toStringify))
t.not(validate(JSON.parse(output)), 'valid schema')
})
'use strict'
const t = require('tap')
const { DateTime } = require('luxon')
const build = require('..')
process.env.TZ = 'UTC'
const schema = {

@@ -384,5 +385,5 @@ type: 'object',

const date = new Date()
const date = new Date(1674263005800)
t.equal(stringify(date), `"${DateTime.fromJSDate(date).toISODate()}"`)
t.equal(stringify(date), '"2023-01-21"')
t.equal(stringify('Invalid'), '"Invalid"')

@@ -389,0 +390,0 @@ })

@@ -48,2 +48,10 @@ 'use strict'

{ input: -45.05, output: '-45' },
{ input: Math.PI, output: '3', rounding: 'trunc' },
{ input: 5.0, output: '5', rounding: 'trunc' },
{ input: null, output: '0', rounding: 'trunc' },
{ input: 0, output: '0', rounding: 'trunc' },
{ input: 0.0, output: '0', rounding: 'trunc' },
{ input: 42, output: '42', rounding: 'trunc' },
{ input: 1.99999, output: '1', rounding: 'trunc' },
{ input: -45.05, output: '-45', rounding: 'trunc' },
{ input: 0.95, output: '1', rounding: 'ceil' },

@@ -50,0 +58,0 @@ { input: 0.2, output: '1', rounding: 'ceil' },

@@ -151,1 +151,19 @@ 'use strict'

})
test('patternProperties - fail on invalid regex, handled by ajv', (t) => {
t.plan(1)
t.throws(() => build({
title: 'check array coerce',
type: 'object',
properties: {},
patternProperties: {
'foo/\\': {
type: 'array',
items: {
type: 'string'
}
}
}
}), new Error('schema is invalid: data/patternProperties must match format "regex"'))
})

@@ -2008,1 +2008,74 @@ 'use strict'

})
test('should throw an Error if two non-identical schemas with same id are provided', (t) => {
t.plan(1)
const schema = {
$id: 'schema',
type: 'object',
allOf: [
{
$id: 'base',
type: 'object',
properties: {
name: {
type: 'string'
}
},
required: [
'name'
]
},
{
$id: 'inner_schema',
type: 'object',
properties: {
union: {
$id: '#id',
anyOf: [
{
$id: 'guid',
type: 'string'
},
{
$id: 'email',
type: 'string'
}
]
}
},
required: [
'union'
]
},
{
$id: 'inner_schema',
type: 'object',
properties: {
union: {
$id: '#id',
anyOf: [
{
$id: 'guid',
type: 'string'
},
{
$id: 'mail',
type: 'string'
}
]
}
},
required: [
'union'
]
}
]
}
t.throws(() => build(schema), new Error('There is already another schema with id inner_schema'))
})

@@ -485,5 +485,51 @@ 'use strict'

const stringify = build(schema)
t.throws(() => stringify({ arr: null }), new TypeError('The value \'null\' does not match schema definition.'))
t.throws(() => stringify({ arr: null }), new TypeError('The value of \'#/properties/arr\' does not match schema definition.'))
})
test('should throw an error when type is array and object is not an array', (t) => {
t.plan(1)
const schema = {
type: 'object',
properties: {
arr: {
type: 'array',
items: {
type: 'number'
}
}
}
}
const stringify = build(schema)
t.throws(() => stringify({ arr: { foo: 'hello' } }), new TypeError('The value of \'#/properties/arr\' does not match schema definition.'))
})
test('should throw an error when type is array and object is not an array with external schema', (t) => {
t.plan(1)
const schema = {
type: 'object',
properties: {
arr: {
$ref: 'arrayOfNumbers#/definitions/arr'
}
}
}
const externalSchema = {
arrayOfNumbers: {
definitions: {
arr: {
type: 'array',
items: {
type: 'number'
}
}
}
}
}
const stringify = build(schema, { schema: externalSchema })
t.throws(() => stringify({ arr: { foo: 'hello' } }), new TypeError('The value of \'arrayOfNumbers#/definitions/arr\' does not match schema definition.'))
})
test('throw an error if none of types matches', (t) => {

@@ -490,0 +536,0 @@ t.plan(1)

@@ -161,4 +161,6 @@ import Ajv, { Options as AjvOptions } from "ajv"

* Optionally configure how the integer will be rounded
*
* @default 'trunc'
*/
rounding?: 'ceil' | 'floor' | 'round'
rounding?: 'ceil' | 'floor' | 'round' | 'trunc'
/**

@@ -173,2 +175,18 @@ * @deprecated

mode?: 'debug' | 'standalone'
/**
* Large arrays are defined as arrays containing, by default, `20000`
* elements or more. That value can be adjusted via the option parameter
* `largeArraySize`.
*
* @default 20000
*/
largeArraySize?: number | string | BigInt
/**
* Specify the function on how large Arrays should be stringified.
*
* @default 'default'
*/
largeArrayMechanism?: 'default' | 'json-stringify'
}

@@ -175,0 +193,0 @@

@@ -16,2 +16,8 @@ import Ajv from 'ajv'

build(schema2, { rounding: 'ceil' })
build(schema2, { rounding: 'floor' })
build(schema2, { rounding: 'round' })
build(schema2, { rounding: 'trunc' })
expectError(build(schema2, { rounding: 'invalid' }))
// String schema

@@ -221,1 +227,12 @@ const schema3: Schema = {

expectError(stringify7("a string"));
// largeArrayMechanism
build({}, { largeArrayMechanism: 'json-stringify'} )
build({}, { largeArrayMechanism: 'default'} )
expectError(build({} as Schema, { largeArrayMechanism: 'invalid'} ))
build({}, { largeArraySize: 2000 } )
build({}, { largeArraySize: '2e4' } )
build({}, { largeArraySize: 2n } )
expectError(build({} as Schema, { largeArraySize: ['asdf']} ))

Sorry, the diff of this file is not supported yet

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