Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

fast-json-stringify

Package Overview
Dependencies
Maintainers
9
Versions
160
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fast-json-stringify - npm Package Compare versions

Comparing version 5.1.0 to 5.2.0

ref-resolver.js

54

benchmark/bench.js

@@ -216,2 +216,56 @@ 'use strict'

}
},
{
name: 'object with const string property',
schema: {
type: 'object',
properties: {
a: { const: 'const string' }
}
},
input: { a: 'const string' }
},
{
name: 'object with const number property',
schema: {
type: 'object',
properties: {
a: { const: 1 }
}
},
input: { a: 1 }
},
{
name: 'object with const bool property',
schema: {
type: 'object',
properties: {
a: { const: true }
}
},
input: { a: true }
},
{
name: 'object with const object property',
schema: {
type: 'object',
properties: {
foo: { const: { bar: 'baz' } }
}
},
input: {
foo: { bar: 'baz' }
}
},
{
name: 'object with const null property',
schema: {
type: 'object',
properties: {
foo: { const: null }
}
},
input: {
foo: null
}
}

@@ -218,0 +272,0 @@ ]

196

index.js

@@ -12,3 +12,4 @@ 'use strict'

const Serializer = require('./serializer')
const buildAjv = require('./ajv')
const Validator = require('./validator')
const RefResolver = require('./ref-resolver')

@@ -61,16 +62,8 @@ let largeArraySize = 2e4

const schemaRef = schemaId + jsonPointer
const schema = refResolver.getSchema(schemaId, jsonPointer)
let ajvSchema
try {
ajvSchema = ajvInstance.getSchema(schemaRef)
} catch (error) {
if (schema === undefined) {
throw new Error(`Cannot find reference "${ref}"`)
}
if (ajvSchema === undefined) {
throw new Error(`Cannot find reference "${ref}"`)
}
const schema = ajvSchema.schema
if (schema.$ref !== undefined) {

@@ -87,8 +80,7 @@ return resolveRef({ schema, schemaId, jsonPointer }, schema.$ref)

let rootSchemaId = null
let ajvInstance = null
let refResolver = null
let validator = null
let contextFunctions = null
function build (schema, options) {
schema = clone(schema)
arrayItemsReferenceSerializersMap.clear()

@@ -100,25 +92,16 @@ objectReferenceSerializersMap.clear()

ajvInstance = buildAjv(options.ajv)
refResolver = new RefResolver()
validator = new Validator(options.ajv)
rootSchemaId = schema.$id || randomUUID()
isValidSchema(schema)
extendDateTimeType(schema)
ajvInstance.addSchema(schema, rootSchemaId)
validator.addSchema(schema, rootSchemaId)
refResolver.addSchema(schema, rootSchemaId)
if (options.schema) {
const externalSchemas = clone(options.schema)
for (const key of Object.keys(externalSchemas)) {
const externalSchema = externalSchemas[key]
isValidSchema(externalSchema, key)
extendDateTimeType(externalSchema)
let schemaKey = externalSchema.$id || key
if (externalSchema.$id !== undefined && externalSchema.$id[0] === '#') {
schemaKey = key + externalSchema.$id // relative URI
}
if (ajvInstance.getSchema(schemaKey) === undefined) {
ajvInstance.addSchema(externalSchema, schemaKey)
}
for (const key of Object.keys(options.schema)) {
isValidSchema(options.schema[key], key)
validator.addSchema(options.schema[key], key)
refResolver.addSchema(options.schema[key], key)
}

@@ -164,3 +147,3 @@ }

const dependenciesName = ['ajv', 'serializer', contextFunctionCode]
const dependenciesName = ['validator', 'serializer', contextFunctionCode]

@@ -172,3 +155,3 @@ if (options.debugMode) {

if (options.mode === 'debug') {
return { code: dependenciesName.join('\n'), ajv: ajvInstance }
return { code: dependenciesName.join('\n'), validator, ajv: validator.ajv }
}

@@ -179,10 +162,11 @@

const buildStandaloneCode = require('./standalone')
return buildStandaloneCode(options, ajvInstance, contextFunctionCode)
return buildStandaloneCode(options, validator, contextFunctionCode)
}
/* eslint no-new-func: "off" */
const contextFunc = new Function('ajv', 'serializer', contextFunctionCode)
const stringifyFunc = contextFunc(ajvInstance, serializer)
const contextFunc = new Function('validator', 'serializer', contextFunctionCode)
const stringifyFunc = contextFunc(validator, serializer)
ajvInstance = null
refResolver = null
validator = null
rootSchemaId = null

@@ -351,13 +335,12 @@ contextFunctions = null

let propertyLocation = mergeLocation(propertiesLocation, key)
if (schema.properties[key].$ref) {
propertyLocation = resolveRef(location, schema.properties[key].$ref)
schema.properties[key] = propertyLocation.schema
if (propertyLocation.$ref) {
propertyLocation = resolveRef(location, propertyLocation.$ref)
}
const sanitized = JSON.stringify(key)
const asString = JSON.stringify(sanitized)
// Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
const sanitized = JSON.stringify(key)
const asString = JSON.stringify(sanitized)
code += `

@@ -371,3 +354,3 @@ if (obj[${sanitized}] !== undefined) {

const defaultValue = schema.properties[key].default
const defaultValue = propertyLocation.schema.default
if (defaultValue !== undefined) {

@@ -487,12 +470,2 @@ code += `

if (allOfSchema.fjs_type !== undefined) {
if (
mergedSchema.fjs_type !== undefined &&
mergedSchema.fjs_type !== allOfSchema.fjs_type
) {
throw new Error('allOf schemas have different fjs_type values')
}
mergedSchema.fjs_type = allOfSchema.fjs_type
}
if (allOfSchema.allOf !== undefined) {

@@ -505,3 +478,4 @@ mergeAllOfSchema(location, allOfSchema, mergedSchema)

mergedSchema.$id = `merged_${randomUUID()}`
ajvInstance.addSchema(mergedSchema)
validator.addSchema(mergedSchema)
refResolver.addSchema(mergedSchema)
location.schemaId = mergedSchema.$id

@@ -535,3 +509,3 @@ location.jsonPointer = '#'

let code = `
if (ajv.validate("${ifSchemaRef}", obj)) {
if (validator.validate("${ifSchemaRef}", obj)) {
`

@@ -810,4 +784,4 @@

let type = schema.type
const nullable = schema.nullable === true
const type = schema.type
const nullable = schema.nullable === true || (Array.isArray(type) && type.includes('null'))

@@ -817,4 +791,11 @@ let code = ''

if (schema.fjs_type === 'string' && schema.format === undefined && Array.isArray(schema.type) && schema.type.length === 2) {
type = 'string'
if ('const' in schema) {
if (nullable) {
code += `
json += ${input} === null ? 'null' : '${JSON.stringify(schema.const)}'
`
return code
}
code += `json += '${JSON.stringify(schema.const)}'`
return code
}

@@ -827,3 +808,11 @@

case 'string': {
funcName = nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
if (schema.format === 'date-time') {
funcName = nullable ? 'serializer.asDateTimeNullable.bind(serializer)' : 'serializer.asDateTime.bind(serializer)'
} else if (schema.format === 'date') {
funcName = nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
} else if (schema.format === 'time') {
funcName = nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
} else {
funcName = nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
}
code += `json += ${funcName}(${input})`

@@ -845,11 +834,3 @@ break

case 'object':
if (schema.format === 'date-time') {
funcName = nullable ? 'serializer.asDateTimeNullable.bind(serializer)' : 'serializer.asDateTime.bind(serializer)'
} else if (schema.format === 'date') {
funcName = nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
} else if (schema.format === 'time') {
funcName = nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
} else {
funcName = buildObject(location)
}
funcName = buildObject(location)
code += `json += ${funcName}(${input})`

@@ -872,3 +853,3 @@ break

code += `
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaRef}", ${input}))
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
${nestedResult}

@@ -885,15 +866,6 @@ `

`
} else if ('const' in schema) {
} else {
code += `
if(ajv.validate(${JSON.stringify(schema)}, ${input}))
json += '${JSON.stringify(schema.const)}'
else
throw new Error(\`Item $\{JSON.stringify(${input})} does not match schema definition.\`)
`
} else if (schema.type === undefined) {
code += `
json += JSON.stringify(${input})
`
} else {
throw new Error(`${schema.type} unsupported`)
}

@@ -922,3 +894,14 @@ break

code += `
${statement}(${input} === null || typeof ${input} === "${type}" || ${input} instanceof RegExp || (typeof ${input} === "object" && Object.hasOwnProperty.call(${input}, "toString")))
${statement}(
typeof ${input} === "string" ||
${input} === null ||
${input} instanceof Date ||
${input} instanceof RegExp ||
(
typeof ${input} === "object" &&
typeof ${input}.toString === "function" &&
${input}.toString !== Object.prototype.toString &&
!(${input} instanceof Date)
)
)
${nestedResult}

@@ -943,13 +926,6 @@ `

case 'object': {
if (schema.fjs_type) {
code += `
${statement}(${input} instanceof Date || ${input} === null)
${nestedResult}
`
} else {
code += `
${statement}(typeof ${input} === "object" || ${input} === null)
${nestedResult}
`
}
code += `
${statement}(typeof ${input} === "object" || ${input} === null)
${nestedResult}
`
break

@@ -983,30 +959,6 @@ }

// 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_type.
// (see https://github.com/fastify/fast-json-stringify/pull/441)
function extendDateTimeType (schema) {
if (schema === null) return
if (schema.type === 'string') {
schema.fjs_type = 'string'
schema.type = ['string', 'object']
} else if (
Array.isArray(schema.type) &&
schema.type.includes('string') &&
!schema.type.includes('object')
) {
schema.fjs_type = 'string'
schema.type.push('object')
}
for (const property in schema) {
if (typeof schema[property] === 'object') {
extendDateTimeType(schema[property])
}
}
}
function isEmpty (schema) {
// eslint-disable-next-line
for (var key in schema) {
if (schema.hasOwnProperty(key) && schema[key] !== undefined) {
if (Object.prototype.hasOwnProperty.call(schema, key) && schema[key] !== undefined) {
return false

@@ -1022,7 +974,7 @@ }

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

@@ -36,2 +36,3 @@ "main": "index.js",

"devDependencies": {
"@fastify/pre-commit": "^2.0.2",
"@sinclair/typebox": "^0.24.9",

@@ -42,4 +43,3 @@ "benchmark": "^2.1.4",

"is-my-json-valid": "^2.20.0",
"luxon": "^2.4.0",
"pre-commit": "^1.2.2",
"luxon": "^3.0.1",
"proxyquire": "^2.1.3",

@@ -57,2 +57,3 @@ "semver": "^7.1.0",

"ajv-formats": "^2.1.1",
"fast-deep-equal": "^3.1.3",
"fast-uri": "^2.1.0",

@@ -59,0 +60,0 @@ "rfdc": "^1.2.0"

@@ -77,2 +77,5 @@ 'use strict'

}
if (typeof date === 'string') {
return '"' + date + '"'
}
throw new Error(`The value "${date}" cannot be converted to a date-time.`)

@@ -90,2 +93,5 @@ }

}
if (typeof date === 'string') {
return '"' + date + '"'
}
throw new Error(`The value "${date}" cannot be converted to a date.`)

@@ -103,2 +109,5 @@ }

}
if (typeof date === 'string') {
return '"' + date + '"'
}
throw new Error(`The value "${date}" cannot be converted to a time.`)

@@ -105,0 +114,0 @@ }

const fs = require('fs')
const path = require('path')
function buildStandaloneCode (options, ajvInstance, contextFunctionCode) {
function buildStandaloneCode (options, validator, contextFunctionCode) {
const serializerCode = fs.readFileSync(path.join(__dirname, 'serializer.js')).toString()
let buildAjvCode = ''
let defaultAjvSchema = ''
const defaultMeta = ajvInstance.defaultMeta()
const defaultMeta = validator.ajv.defaultMeta()
if (typeof defaultMeta === 'string') {

@@ -14,14 +14,14 @@ defaultAjvSchema = defaultMeta

}
const shouldUseAjv = contextFunctionCode.indexOf('ajv') !== -1
const shouldUseAjv = contextFunctionCode.indexOf('validator') !== -1
// we need to export the custom json schema
let ajvSchemasCode = ''
if (shouldUseAjv) {
ajvSchemasCode += `const ajv = buildAjv(${JSON.stringify(options.ajv || {})})\n`
for (const [id, schema] of Object.entries(ajvInstance.schemas)) {
ajvSchemasCode += `const validator = new Validator(${JSON.stringify(options.ajv || {})})\n`
for (const [id, schema] of Object.entries(validator.ajv.schemas)) {
// should skip ajv default schema
if (id === defaultAjvSchema) continue
ajvSchemasCode += `ajv.addSchema(${JSON.stringify(schema.schema)}, "${id}")\n`
ajvSchemasCode += `validator.ajv.addSchema(${JSON.stringify(schema.schema)}, "${id}")\n`
}
buildAjvCode = fs.readFileSync(path.join(__dirname, 'ajv.js')).toString()
buildAjvCode = buildAjvCode.replace("'use strict'", '').replace('module.exports = buildAjv', '')
buildAjvCode = fs.readFileSync(path.join(__dirname, 'validator.js')).toString()
buildAjvCode = buildAjvCode.replace("'use strict'", '').replace('module.exports = SchemaValidator', '')
}

@@ -28,0 +28,0 @@ return `

@@ -27,2 +27,180 @@ 'use strict'

test('schema with const string and different input', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
foo: { const: 'bar' }
}
}
const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 'baz'
})
t.equal(output, '{"foo":"bar"}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('schema with const string and different type input', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
foo: { const: 'bar' }
}
}
const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 1
})
t.equal(output, '{"foo":"bar"}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('schema with const string and no input', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
foo: { const: 'bar' }
}
}
const validate = validator(schema)
const stringify = build(schema)
const output = stringify({})
t.equal(output, '{}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('schema with const number', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
foo: { const: 1 }
}
}
const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 1
})
t.equal(output, '{"foo":1}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('schema with const number and different input', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
foo: { const: 1 }
}
}
const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 2
})
t.equal(output, '{"foo":1}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('schema with const bool', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
foo: { const: true }
}
}
const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: true
})
t.equal(output, '{"foo":true}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('schema with const number', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
foo: { const: 1 }
}
}
const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 1
})
t.equal(output, '{"foo":1}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('schema with const null', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
foo: { const: null }
}
}
const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: null
})
t.equal(output, '{"foo":null}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('schema with const array', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
foo: { const: [1, 2, 3] }
}
}
const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: [1, 2, 3]
})
t.equal(output, '{"foo":[1,2,3]}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})
test('schema with const object', (t) => {

@@ -48,2 +226,52 @@ t.plan(2)

test('schema with const and null as type', (t) => {
t.plan(4)
const schema = {
type: 'object',
properties: {
foo: { type: ['string', 'null'], const: 'baz' }
}
}
const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: null
})
t.equal(output, '{"foo":null}')
t.ok(validate(JSON.parse(output)), 'valid schema')
const output2 = stringify({ foo: 'baz' })
t.equal(output2, '{"foo":"baz"}')
t.ok(validate(JSON.parse(output2)), 'valid schema')
})
test('schema with const as nullable', (t) => {
t.plan(4)
const schema = {
type: 'object',
properties: {
foo: { nullable: true, const: 'baz' }
}
}
const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: null
})
t.equal(output, '{"foo":null}')
t.ok(validate(JSON.parse(output)), 'valid schema')
const output2 = stringify({
foo: 'baz'
})
t.equal(output2, '{"foo":"baz"}')
t.ok(validate(JSON.parse(output2)), 'valid schema')
})
test('schema with const and invalid object', (t) => {

@@ -60,11 +288,10 @@ t.plan(2)

const validate = validator(schema)
const stringify = build(schema)
try {
stringify({
foo: { foo: 'baz' }
})
} catch (err) {
t.match(err.message, /^Item .* does not match schema definition/, 'Given object has invalid const value')
t.ok(err)
}
const result = stringify({
foo: { foo: 'baz' }
})
t.equal(result, '{"foo":{"foo":"bar"}}')
t.ok(validate(JSON.parse(result)), 'valid schema')
})

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

const fjs = require('..')
const Ajv = require('ajv').default
const Validator = require('../validator')

@@ -22,3 +24,3 @@ function build (opts) {

test('activate debug mode', t => {
t.plan(3)
t.plan(4)
const debugMode = build({ debugMode: true })

@@ -28,2 +30,3 @@

t.ok(debugMode.ajv instanceof Ajv)
t.ok(debugMode.validator instanceof Validator)
t.type(debugMode.code, 'string')

@@ -33,3 +36,3 @@ })

test('activate debug mode truthy', t => {
t.plan(3)
t.plan(4)

@@ -41,6 +44,7 @@ const debugMode = build({ debugMode: 'yes' })

t.ok(debugMode.ajv instanceof Ajv)
t.ok(debugMode.validator instanceof Validator)
})
test('to string auto-consistent', t => {
t.plan(4)
t.plan(5)
const debugMode = build({ debugMode: 1 })

@@ -51,2 +55,3 @@

t.ok(debugMode.ajv instanceof Ajv)
t.ok(debugMode.validator instanceof Validator)

@@ -59,3 +64,3 @@ const compiled = fjs.restore(debugMode)

test('to string auto-consistent with ajv', t => {
t.plan(4)
t.plan(5)

@@ -79,2 +84,3 @@ const debugMode = fjs({

t.ok(debugMode.ajv instanceof Ajv)
t.ok(debugMode.validator instanceof Validator)

@@ -81,0 +87,0 @@ const compiled = fjs.restore(debugMode)

@@ -447,1 +447,98 @@ 'use strict'

})
test('nullable type in the schema', (t) => {
t.plan(2)
const schema = {
type: ['object', 'null'],
properties: {
foo: {
type: 'string'
}
}
}
const stringify = build(schema)
const data = { foo: 'bar' }
t.same(stringify(data), JSON.stringify(data))
t.same(stringify(null), JSON.stringify(null))
})
test('throw an error if the value doesn\'t match the type', (t) => {
t.plan(2)
const schema = {
type: 'object',
additionalProperties: false,
required: ['data'],
properties: {
data: {
type: 'array',
minItems: 1,
items: {
oneOf: [
{
type: 'string'
},
{
type: 'number'
}
]
}
}
}
}
const stringify = build(schema)
const validData = { data: [1, 'testing'] }
t.equal(stringify(validData), JSON.stringify(validData))
const invalidData = { data: [false, 'testing'] }
t.throws(() => stringify(invalidData))
})
test('nullable value in oneOf', (t) => {
t.plan(1)
const schema = {
type: 'object',
properties: {
data: {
oneOf: [
{
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'integer', minimum: 1 }
},
additionalProperties: false,
required: ['id']
}
},
{
type: 'array',
items: {
type: 'object',
properties: {
job: { type: 'string', nullable: true }
},
additionalProperties: false,
required: ['job']
}
}
]
}
},
required: ['data'],
additionalProperties: false
}
const stringify = build(schema)
const data = { data: [{ job: null }] }
t.equal(stringify(data), JSON.stringify(data))
})

@@ -1956,1 +1956,29 @@ 'use strict'

})
test('nested schema should overwrite anchor scope', (t) => {
t.plan(2)
const externalSchema = {
root: {
$id: 'root',
definitions: {
subschema: {
$id: 'subschema',
definitions: {
anchorSchema: {
$id: '#anchor',
type: 'string'
}
}
}
}
}
}
const data = 'test'
const stringify = build({ $ref: 'subschema#anchor' }, { schema: externalSchema })
const output = stringify(data)
t.equal(output, JSON.stringify(data))
t.throws(() => build({ $ref: 'root#anchor' }, { schema: externalSchema }))
})

@@ -441,2 +441,31 @@ 'use strict'

test('class instance that is simultaneously a string and a json', (t) => {
t.plan(2)
const schema = {
type: 'object',
properties: {
simultaneously: {
type: ['string', 'object'],
properties: {
foo: { type: 'string' }
}
}
}
}
class Test {
toString () { return 'hello' }
}
const likeObjectId = new Test()
const stringify = build(schema)
const valueStr = stringify({ simultaneously: likeObjectId })
t.equal(valueStr, '{"simultaneously":"hello"}')
const valueObj = stringify({ simultaneously: { foo: likeObjectId } })
t.equal(valueObj, '{"simultaneously":{"foo":"hello"}}')
})
test('should throw an error when type is array and object is null', (t) => {

@@ -443,0 +472,0 @@ t.plan(1)

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
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc