fast-json-stringify
Advanced tools
Comparing version 5.2.0 to 5.3.0
import Ajv, { Options as AjvOptions } from "ajv" | ||
declare namespace build { | ||
@@ -192,11 +193,11 @@ interface BaseSchema { | ||
declare function build(schema: build.AnySchema, options: StandaloneOption): string; | ||
declare function build(schema: build.AnySchema, options?: build.Options): (doc: any) => any; | ||
declare function build(schema: build.StringSchema, options?: build.Options): (doc: string) => string; | ||
declare function build(schema: build.IntegerSchema | build.NumberSchema, options?: build.Options): (doc: number) => string; | ||
declare function build(schema: build.NullSchema, options?: build.Options): (doc: null) => "null"; | ||
declare function build(schema: build.BooleanSchema, options?: build.Options): (doc: boolean) => string; | ||
declare function build(schema: build.ArraySchema | build.TupleSchema, options?: build.Options): (doc: any[]) => string; | ||
declare function build(schema: build.ObjectSchema, options?: build.Options): (doc: object) => string; | ||
declare function build(schema: build.Schema, options?: build.Options): (doc: object | any[] | string | number | boolean | null) => string; | ||
declare function build(schema: build.AnySchema, options?: build.Options): <TDoc = any>(doc: TDoc) => any; | ||
declare function build(schema: build.StringSchema, options?: build.Options): <TDoc extends string = string>(doc: TDoc) => string; | ||
declare function build(schema: build.IntegerSchema | build.NumberSchema, options?: build.Options): <TDoc extends number = number>(doc: TDoc) => string; | ||
declare function build(schema: build.NullSchema, options?: build.Options): <TDoc extends null = null>(doc: TDoc) => "null"; | ||
declare function build(schema: build.BooleanSchema, options?: build.Options): <TDoc extends boolean = boolean>(doc: TDoc) => string; | ||
declare function build(schema: build.ArraySchema | build.TupleSchema, options?: build.Options): <TDoc extends any[]= any[]>(doc: TDoc) => string; | ||
declare function build(schema: build.ObjectSchema, options?: build.Options): <TDoc extends object = object>(doc: TDoc) => string; | ||
declare function build(schema: build.Schema, options?: build.Options): <TDoc = object | any[] | string | number | boolean | null> (doc: TDoc) => string; | ||
export = build; |
411
index.js
@@ -7,9 +7,8 @@ 'use strict' | ||
const clone = require('rfdc')({ proto: true }) | ||
const fjsCloned = Symbol('fast-json-stringify.cloned') | ||
const { randomUUID } = require('crypto') | ||
const validate = require('./schema-validator') | ||
const Serializer = require('./serializer') | ||
const Validator = require('./validator') | ||
const RefResolver = require('./ref-resolver') | ||
const validate = require('./lib/schema-validator') | ||
const Serializer = require('./lib/serializer') | ||
const Validator = require('./lib/validator') | ||
const RefResolver = require('./lib/ref-resolver') | ||
@@ -75,4 +74,3 @@ let largeArraySize = 2e4 | ||
const arrayItemsReferenceSerializersMap = new Map() | ||
const objectReferenceSerializersMap = new Map() | ||
const contextFunctionsNamesBySchema = new Map() | ||
@@ -85,4 +83,3 @@ let rootSchemaId = null | ||
function build (schema, options) { | ||
arrayItemsReferenceSerializersMap.clear() | ||
objectReferenceSerializersMap.clear() | ||
contextFunctionsNamesBySchema.clear() | ||
@@ -153,3 +150,8 @@ contextFunctions = [] | ||
if (options.mode === 'debug') { | ||
return { code: dependenciesName.join('\n'), validator, ajv: validator.ajv } | ||
return { | ||
validator, | ||
serializer, | ||
code: dependenciesName.join('\n'), | ||
ajv: validator.ajv | ||
} | ||
} | ||
@@ -159,3 +161,3 @@ | ||
// lazy load | ||
const buildStandaloneCode = require('./standalone') | ||
const buildStandaloneCode = require('./lib/standalone') | ||
return buildStandaloneCode(options, validator, contextFunctionCode) | ||
@@ -172,4 +174,3 @@ } | ||
contextFunctions = null | ||
arrayItemsReferenceSerializersMap.clear() | ||
objectReferenceSerializersMap.clear() | ||
contextFunctionsNamesBySchema.clear() | ||
@@ -547,8 +548,8 @@ return stringifyFunc | ||
if (objectReferenceSerializersMap.has(schema)) { | ||
return objectReferenceSerializersMap.get(schema) | ||
if (contextFunctionsNamesBySchema.has(schema)) { | ||
return contextFunctionsNamesBySchema.get(schema) | ||
} | ||
const functionName = generateFuncName() | ||
objectReferenceSerializersMap.set(schema, functionName) | ||
contextFunctionsNamesBySchema.set(schema, functionName) | ||
@@ -560,9 +561,2 @@ const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId | ||
` | ||
if (schema.nullable) { | ||
functionCode += ` | ||
if (input === null) { | ||
return 'null'; | ||
} | ||
` | ||
} | ||
@@ -592,29 +586,19 @@ functionCode += ` | ||
function buildArray (location) { | ||
let schema = location.schema | ||
const schema = location.schema | ||
// default to any items type | ||
if (!schema.items) { | ||
schema.items = {} | ||
} | ||
let itemsLocation = mergeLocation(location, 'items') | ||
itemsLocation.schema = itemsLocation.schema || {} | ||
if (schema.items.$ref) { | ||
if (!schema[fjsCloned]) { | ||
location.schema = clone(location.schema) | ||
schema = location.schema | ||
schema[fjsCloned] = true | ||
} | ||
location = resolveRef(location, schema.items.$ref) | ||
itemsLocation = location | ||
schema.items = location.schema | ||
if (itemsLocation.schema.$ref) { | ||
itemsLocation = resolveRef(itemsLocation, itemsLocation.schema.$ref) | ||
} | ||
if (arrayItemsReferenceSerializersMap.has(schema.items)) { | ||
return arrayItemsReferenceSerializersMap.get(schema.items) | ||
const itemsSchema = itemsLocation.schema | ||
if (contextFunctionsNamesBySchema.has(schema)) { | ||
return contextFunctionsNamesBySchema.get(schema) | ||
} | ||
const functionName = generateFuncName() | ||
arrayItemsReferenceSerializersMap.set(schema.items, functionName) | ||
contextFunctionsNamesBySchema.set(schema, functionName) | ||
@@ -627,10 +611,2 @@ const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId | ||
if (schema.nullable) { | ||
functionCode += ` | ||
if (obj === null) { | ||
return 'null'; | ||
} | ||
` | ||
} | ||
functionCode += ` | ||
@@ -645,4 +621,4 @@ if (!Array.isArray(obj)) { | ||
functionCode += ` | ||
if (arrayLength > ${schema.items.length}) { | ||
throw new Error(\`Item at ${schema.items.length} does not match schema definition.\`) | ||
if (arrayLength > ${itemsSchema.length}) { | ||
throw new Error(\`Item at ${itemsSchema.length} does not match schema definition.\`) | ||
} | ||
@@ -664,5 +640,5 @@ ` | ||
if (Array.isArray(schema.items)) { | ||
for (let i = 0; i < schema.items.length; i++) { | ||
const item = schema.items[i] | ||
if (Array.isArray(itemsSchema)) { | ||
for (let i = 0; i < itemsSchema.length; i++) { | ||
const item = itemsSchema[i] | ||
const tmpRes = buildValue(mergeLocation(itemsLocation, i), `obj[${i}]`) | ||
@@ -687,3 +663,3 @@ functionCode += ` | ||
functionCode += ` | ||
for (let i = ${schema.items.length}; i < arrayLength; i++) { | ||
for (let i = ${itemsSchema.length}; i < arrayLength; i++) { | ||
let json = JSON.stringify(obj[i]) | ||
@@ -759,2 +735,133 @@ jsonOutput += json | ||
function buildMultiTypeSerializer (location, input) { | ||
const schema = location.schema | ||
const types = schema.type.sort(t1 => t1 === 'null' ? -1 : 1) | ||
let code = '' | ||
const locationClone = clone(location) | ||
types.forEach((type, index) => { | ||
const statement = index === 0 ? 'if' : 'else if' | ||
locationClone.schema.type = type | ||
const nestedResult = buildSingleTypeSerializer(locationClone, input) | ||
switch (type) { | ||
case 'null': | ||
code += ` | ||
${statement} (${input} === null) | ||
${nestedResult} | ||
` | ||
break | ||
case 'string': { | ||
code += ` | ||
${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} | ||
` | ||
break | ||
} | ||
case 'array': { | ||
code += ` | ||
${statement}(Array.isArray(${input})) | ||
${nestedResult} | ||
` | ||
break | ||
} | ||
case 'integer': { | ||
code += ` | ||
${statement}(Number.isInteger(${input}) || ${input} === null) | ||
${nestedResult} | ||
` | ||
break | ||
} | ||
default: { | ||
code += ` | ||
${statement}(typeof ${input} === "${type}" || ${input} === null) | ||
${nestedResult} | ||
` | ||
break | ||
} | ||
} | ||
}) | ||
code += ` | ||
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`) | ||
` | ||
return code | ||
} | ||
function buildSingleTypeSerializer (location, input) { | ||
const schema = location.schema | ||
switch (schema.type) { | ||
case 'null': | ||
return 'json += \'null\'' | ||
case 'string': { | ||
if (schema.format === 'date-time') { | ||
return `json += serializer.asDateTime(${input})` | ||
} else if (schema.format === 'date') { | ||
return `json += serializer.asDate(${input})` | ||
} else if (schema.format === 'time') { | ||
return `json += serializer.asTime(${input})` | ||
} else { | ||
return `json += serializer.asString(${input})` | ||
} | ||
} | ||
case 'integer': | ||
return `json += serializer.asInteger(${input})` | ||
case 'number': | ||
return `json += serializer.asNumber(${input})` | ||
case 'boolean': | ||
return `json += serializer.asBoolean(${input})` | ||
case 'object': { | ||
const funcName = buildObject(location) | ||
return `json += ${funcName}(${input})` | ||
} | ||
case 'array': { | ||
const funcName = buildArray(location) | ||
return `json += ${funcName}(${input})` | ||
} | ||
case undefined: | ||
return `json += JSON.stringify(${input})` | ||
default: | ||
throw new Error(`${schema.type} unsupported`) | ||
} | ||
} | ||
function buildConstSerializer (location, input) { | ||
const schema = location.schema | ||
const type = schema.type | ||
const hasNullType = Array.isArray(type) && type.includes('null') | ||
let code = '' | ||
if (hasNullType) { | ||
code += ` | ||
if (${input} === null) { | ||
json += 'null' | ||
} else { | ||
` | ||
} | ||
code += `json += '${JSON.stringify(schema.const)}'` | ||
if (hasNullType) { | ||
code += ` | ||
} | ||
` | ||
} | ||
return code | ||
} | ||
function buildValue (location, input) { | ||
@@ -787,163 +894,46 @@ let schema = location.schema | ||
const type = schema.type | ||
const nullable = schema.nullable === true || (Array.isArray(type) && type.includes('null')) | ||
let code = '' | ||
let funcName | ||
if ('const' in schema) { | ||
if (nullable) { | ||
if (type === undefined && (schema.anyOf || schema.oneOf)) { | ||
const type = schema.anyOf ? 'anyOf' : 'oneOf' | ||
const anyOfLocation = mergeLocation(location, type) | ||
for (let index = 0; index < location.schema[type].length; index++) { | ||
const optionLocation = mergeLocation(anyOfLocation, index) | ||
const schemaRef = optionLocation.schemaId + optionLocation.jsonPointer | ||
const nestedResult = buildValue(optionLocation, input) | ||
code += ` | ||
json += ${input} === null ? 'null' : '${JSON.stringify(schema.const)}' | ||
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input})) | ||
${nestedResult} | ||
` | ||
return code | ||
} | ||
code += `json += '${JSON.stringify(schema.const)}'` | ||
code += ` | ||
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`) | ||
` | ||
return code | ||
} | ||
switch (type) { | ||
case 'null': | ||
code += 'json += serializer.asNull()' | ||
break | ||
case 'string': { | ||
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)' | ||
const nullable = schema.nullable === true | ||
if (nullable) { | ||
code += ` | ||
if (${input} === null) { | ||
json += 'null' | ||
} else { | ||
funcName = nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)' | ||
} | ||
code += `json += ${funcName}(${input})` | ||
break | ||
} | ||
case 'integer': | ||
funcName = nullable ? 'serializer.asIntegerNullable.bind(serializer)' : 'serializer.asInteger.bind(serializer)' | ||
code += `json += ${funcName}(${input})` | ||
break | ||
case 'number': | ||
funcName = nullable ? 'serializer.asNumberNullable.bind(serializer)' : 'serializer.asNumber.bind(serializer)' | ||
code += `json += ${funcName}(${input})` | ||
break | ||
case 'boolean': | ||
funcName = nullable ? 'serializer.asBooleanNullable.bind(serializer)' : 'serializer.asBoolean.bind(serializer)' | ||
code += `json += ${funcName}(${input})` | ||
break | ||
case 'object': | ||
funcName = buildObject(location) | ||
code += `json += ${funcName}(${input})` | ||
break | ||
case 'array': | ||
funcName = buildArray(location) | ||
code += `json += ${funcName}(${input})` | ||
break | ||
case undefined: | ||
if (schema.anyOf || schema.oneOf) { | ||
// beware: dereferenceOfRefs has side effects and changes schema.anyOf | ||
const type = schema.anyOf ? 'anyOf' : 'oneOf' | ||
const anyOfLocation = mergeLocation(location, type) | ||
` | ||
} | ||
for (let index = 0; index < location.schema[type].length; index++) { | ||
const optionLocation = mergeLocation(anyOfLocation, index) | ||
const schemaRef = optionLocation.schemaId + optionLocation.jsonPointer | ||
const nestedResult = buildValue(optionLocation, input) | ||
code += ` | ||
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input})) | ||
${nestedResult} | ||
` | ||
} | ||
if (schema.const !== undefined) { | ||
code += buildConstSerializer(location, input) | ||
} else if (Array.isArray(type)) { | ||
code += buildMultiTypeSerializer(location, input) | ||
} else { | ||
code += buildSingleTypeSerializer(location, input) | ||
} | ||
code += ` | ||
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`) | ||
` | ||
} else if (isEmpty(schema)) { | ||
code += ` | ||
json += JSON.stringify(${input}) | ||
` | ||
} else { | ||
code += ` | ||
json += JSON.stringify(${input}) | ||
` | ||
if (nullable) { | ||
code += ` | ||
} | ||
break | ||
default: | ||
if (Array.isArray(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 {` | ||
} | ||
const locationClone = clone(location) | ||
sortedTypes.forEach((type, index) => { | ||
const statement = index === 0 ? 'if' : 'else if' | ||
locationClone.schema.type = type | ||
const nestedResult = buildValue(locationClone, input) | ||
switch (type) { | ||
case 'string': { | ||
code += ` | ||
${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} | ||
` | ||
break | ||
} | ||
case 'array': { | ||
code += ` | ||
${statement}(Array.isArray(${input})) | ||
${nestedResult} | ||
` | ||
break | ||
} | ||
case 'integer': { | ||
code += ` | ||
${statement}(Number.isInteger(${input}) || ${input} === null) | ||
${nestedResult} | ||
` | ||
break | ||
} | ||
case 'object': { | ||
code += ` | ||
${statement}(typeof ${input} === "object" || ${input} === null) | ||
${nestedResult} | ||
` | ||
break | ||
} | ||
default: { | ||
code += ` | ||
${statement}(typeof ${input} === "${type}" || ${input} === null) | ||
${nestedResult} | ||
` | ||
break | ||
} | ||
} | ||
}) | ||
code += ` | ||
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`) | ||
` | ||
if (nullable) { | ||
code += ` | ||
} | ||
` | ||
} | ||
} else { | ||
throw new Error(`${type} unsupported`) | ||
} | ||
` | ||
} | ||
@@ -954,12 +944,2 @@ | ||
function isEmpty (schema) { | ||
// eslint-disable-next-line | ||
for (var key in schema) { | ||
if (Object.prototype.hasOwnProperty.call(schema, key) && schema[key] !== undefined) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
module.exports = build | ||
@@ -969,4 +949,3 @@ | ||
module.exports.restore = function ({ code, validator }) { | ||
const serializer = new Serializer() | ||
module.exports.restore = function ({ code, validator, serializer }) { | ||
// eslint-disable-next-line | ||
@@ -973,0 +952,0 @@ return (Function.apply(null, ['validator', 'serializer', code]) |
{ | ||
"name": "fast-json-stringify", | ||
"version": "5.2.0", | ||
"version": "5.3.0", | ||
"description": "Stringify your JSON at max speed", | ||
@@ -14,3 +14,3 @@ "main": "index.js", | ||
"test:lint": "standard", | ||
"test:typescript": "tsc --project ./test/types/tsconfig.json", | ||
"test:typescript": "tsc --project ./test/types/tsconfig.json && tsd", | ||
"test:unit": "tap -J test/*.test.js test/**/*.test.js", | ||
@@ -49,2 +49,3 @@ "test": "npm run test:lint && npm run test:unit && npm run test:typescript" | ||
"tap": "^16.0.1", | ||
"tsd": "^0.22.0", | ||
"typescript": "^4.0.2", | ||
@@ -66,3 +67,6 @@ "webpack": "^5.40.0" | ||
}, | ||
"runkitExampleFilename": "example.js" | ||
"runkitExampleFilename": "./examples/example.js", | ||
"tsd": { | ||
"directory": "test/types" | ||
} | ||
} |
@@ -330,2 +330,27 @@ 'use strict' | ||
test('different arrays with same item schemas', (t) => { | ||
t.plan(1) | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
array1: { | ||
type: 'array', | ||
items: [{ type: 'string' }], | ||
additionalItems: false | ||
}, | ||
array2: { | ||
type: 'array', | ||
items: { $ref: '#/properties/array1/items' }, | ||
additionalItems: true | ||
} | ||
} | ||
} | ||
const stringify = build(schema) | ||
const data = { array1: ['bar'], array2: ['foo', 'bar'] } | ||
t.equal(stringify(data), '{"array1":["bar"],"array2":["foo","bar"]}') | ||
}) | ||
const largeArray = new Array(2e4).fill({ a: 'test', b: 1 }) | ||
@@ -332,0 +357,0 @@ buildTest({ |
@@ -528,1 +528,20 @@ 'use strict' | ||
}) | ||
test('non-date format should not affect data serialization (issue #491)', (t) => { | ||
t.plan(1) | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
hello: { | ||
type: 'string', | ||
format: 'int64', | ||
pattern: '^[0-9]*$' | ||
} | ||
} | ||
} | ||
const stringify = build(schema) | ||
const data = { hello: 123n } | ||
t.equal(stringify(data), '{"hello":"123"}') | ||
}) |
@@ -7,3 +7,4 @@ 'use strict' | ||
const Ajv = require('ajv').default | ||
const Validator = require('../validator') | ||
const Validator = require('../lib/validator') | ||
const Serializer = require('../lib/serializer') | ||
@@ -24,3 +25,3 @@ function build (opts) { | ||
test('activate debug mode', t => { | ||
t.plan(4) | ||
t.plan(5) | ||
const debugMode = build({ debugMode: true }) | ||
@@ -31,2 +32,3 @@ | ||
t.ok(debugMode.validator instanceof Validator) | ||
t.ok(debugMode.serializer instanceof Serializer) | ||
t.type(debugMode.code, 'string') | ||
@@ -36,3 +38,3 @@ }) | ||
test('activate debug mode truthy', t => { | ||
t.plan(4) | ||
t.plan(5) | ||
@@ -45,6 +47,7 @@ const debugMode = build({ debugMode: 'yes' }) | ||
t.ok(debugMode.validator instanceof Validator) | ||
t.ok(debugMode.serializer instanceof Serializer) | ||
}) | ||
test('to string auto-consistent', t => { | ||
t.plan(5) | ||
t.plan(6) | ||
const debugMode = build({ debugMode: 1 }) | ||
@@ -55,2 +58,3 @@ | ||
t.ok(debugMode.ajv instanceof Ajv) | ||
t.ok(debugMode.serializer instanceof Serializer) | ||
t.ok(debugMode.validator instanceof Validator) | ||
@@ -64,3 +68,3 @@ | ||
test('to string auto-consistent with ajv', t => { | ||
t.plan(5) | ||
t.plan(6) | ||
@@ -85,2 +89,3 @@ const debugMode = fjs({ | ||
t.ok(debugMode.validator instanceof Validator) | ||
t.ok(debugMode.serializer instanceof Serializer) | ||
@@ -117,1 +122,9 @@ const compiled = fjs.restore(debugMode) | ||
}) | ||
test('debug should restore the same serializer instance', t => { | ||
t.plan(1) | ||
const debugMode = fjs({ type: 'integer' }, { debugMode: 1, rounding: 'ceil' }) | ||
const compiled = fjs.restore(debugMode) | ||
t.same(compiled(3.95), 4) | ||
}) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
328737
78
12294
15