🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

fast-json-stringify

Package Overview
Dependencies
Maintainers
11
Versions
168
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
6.3.0
to
6.4.0
+19
.github/workflows/lock-threads.yml
name: Lock Threads
on:
schedule:
- cron: '0 0 1 * *'
workflow_dispatch:
concurrency:
group: lock
permissions:
contents: read
jobs:
lock-threads:
permissions:
issues: write
pull-requests: write
uses: fastify/workflows/.github/workflows/lock-threads.yml@v6
'use strict'
const { test } = require('node:test')
const build = require('..')
test('external $ref schema should be reused, not inlined at every reference', (t) => {
t.plan(3)
const contactSchema = {
$id: 'contact.json',
type: 'object',
properties: {
firstName: { type: 'string' },
lastName: { type: 'string' },
email: { type: 'string' }
}
}
// Schema referencing the same external $ref multiple times
const parentSchema = {
type: 'object',
properties: {
owner: { $ref: 'contact.json' },
assignee: { $ref: 'contact.json' },
reporter: { $ref: 'contact.json' }
}
}
const serializer = build(parentSchema, { schema: { 'contact.json': contactSchema } })
const code = serializer.toString()
// The serialized function should contain extracted anonymous functions
// rather than inlining the full contact schema at every reference.
// With the fix, `firstName` appears 0 times in the main serializer body
// (it is inside the extracted function, which is not included in .toString())
// or at most 1 time if in a single extracted function.
const firstNameMatches = code.match(/firstName/g)
t.assert.ok(firstNameMatches === null || firstNameMatches.length <= 1,
`firstName should appear at most once (extracted function), got ${firstNameMatches ? firstNameMatches.length : 0}`)
// Verify correct serialization
const data = {
owner: { firstName: 'John', lastName: 'Doe', email: 'john@example.com' },
assignee: { firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com' },
reporter: { firstName: 'Bob', lastName: 'Jones', email: 'bob@example.com' }
}
const output = serializer(data)
t.assert.doesNotThrow(() => JSON.parse(output))
t.assert.equal(output, JSON.stringify(data))
})
test('external $ref schema reused with anyOf wrapping', (t) => {
t.plan(2)
const contactSchema = {
$id: 'contact.json',
type: 'object',
properties: {
firstName: { type: 'string' },
lastName: { type: 'string' },
email: { type: 'string' }
}
}
// Common pattern: anyOf wrapping $ref for polymorphic fields (populated object OR string ID OR null)
const parentSchema = {
type: 'object',
properties: {
owner: {
anyOf: [{ type: ['string', 'null'] }, { $ref: 'contact.json' }]
},
assignee: {
anyOf: [{ type: ['string', 'null'] }, { $ref: 'contact.json' }]
},
reporter: {
anyOf: [{ type: ['string', 'null'] }, { $ref: 'contact.json' }]
}
}
}
const serializer = build(parentSchema, { schema: { 'contact.json': contactSchema } })
// Serialize with populated objects
const data = {
owner: { firstName: 'John', lastName: 'Doe', email: 'john@example.com' },
assignee: { firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com' },
reporter: { firstName: 'Bob', lastName: 'Jones', email: 'bob@example.com' }
}
const output = serializer(data)
t.assert.doesNotThrow(() => JSON.parse(output))
t.assert.equal(output, JSON.stringify(data))
})
test('external $ref schema reused in array items', (t) => {
t.plan(2)
const contactSchema = {
$id: 'contact.json',
type: 'object',
properties: {
firstName: { type: 'string' },
lastName: { type: 'string' }
}
}
const parentSchema = {
type: 'object',
properties: {
contacts: {
type: 'array',
items: { $ref: 'contact.json' }
},
primary: { $ref: 'contact.json' },
secondary: { $ref: 'contact.json' }
}
}
const serializer = build(parentSchema, { schema: { 'contact.json': contactSchema } })
const data = {
contacts: [
{ firstName: 'Alice', lastName: 'Wonder' },
{ firstName: 'Bob', lastName: 'Builder' }
],
primary: { firstName: 'Charlie', lastName: 'Charm' },
secondary: { firstName: 'Diana', lastName: 'Prince' }
}
const output = serializer(data)
t.assert.doesNotThrow(() => JSON.parse(output))
t.assert.equal(output, JSON.stringify(data))
})
test('external array $ref schema should be reused, not inlined at every reference', (t) => {
t.plan(2)
const tagsSchema = {
$id: 'tags.json',
type: 'array',
items: { type: 'string' }
}
const parentSchema = {
type: 'object',
properties: {
a: { $ref: 'tags.json' },
b: { $ref: 'tags.json' },
c: { $ref: 'tags.json' }
}
}
const serializer = build(parentSchema, { schema: { 'tags.json': tagsSchema } })
const data = {
a: ['x', 'y'],
b: ['z'],
c: []
}
const output = serializer(data)
t.assert.doesNotThrow(() => JSON.parse(output))
t.assert.equal(output, JSON.stringify(data))
})
test('inline anonymous schemas should still be inlined (not extracted)', (t) => {
t.plan(1)
// Inline schemas (no external $ref) should continue to work normally
const schema = {
type: 'object',
properties: {
a: { type: 'object', properties: { x: { type: 'string' } } },
b: { type: 'object', properties: { x: { type: 'string' } } }
}
}
const serializer = build(schema)
const output = serializer({ a: { x: 'hello' }, b: { x: 'world' } })
t.assert.equal(output, '{"a":{"x":"hello"},"b":{"x":"world"}}')
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Test using this disabled, see https://github.com/fastify/fast-json-stringify/pull/683
import Ajv from 'ajv'
import build, { restore, Schema, validLargeArrayMechanisms } from '..'
import { expect } from 'tstyche'
// Number schemas
build({
type: 'number'
})(25)
build({
type: 'integer'
})(-5)
build({
type: 'integer'
})(5n)
build({
type: 'number'
}, { rounding: 'ceil' })
build({
type: 'number'
}, { rounding: 'floor' })
build({
type: 'number'
}, { rounding: 'round' })
build({
type: 'number'
}, { rounding: 'trunc' })
expect(build).type.not.toBeCallableWith({
type: 'number'
}, { rounding: 'invalid' })
// String schema
build({
type: 'string'
})('foobar')
// Boolean schema
build({
type: 'boolean'
})(true)
// Null schema
build({
type: 'null'
})(null)
// Array schemas
build({
type: 'array',
items: { type: 'number' }
})([25])
build({
type: 'array',
items: [{ type: 'string' }, { type: 'integer' }]
})(['hello', 42])
// Object schemas
build({
type: 'object'
})({})
build({
type: 'object',
properties: {
foo: { type: 'string' },
bar: { type: 'integer' }
},
required: ['foo'],
patternProperties: {
'baz*': { type: 'null' }
},
additionalProperties: {
type: 'boolean'
}
})({ foo: 'bar' })
build({
type: 'object',
properties: {
foo: { type: 'string' },
bar: { type: 'integer' }
},
required: ['foo'],
patternProperties: {
'baz*': { type: 'null' }
},
additionalProperties: {
type: 'boolean'
}
}, { rounding: 'floor' })({ foo: 'bar' })
// Reference schemas
build({
title: 'Example Schema',
definitions: {
num: {
type: 'object',
properties: {
int: {
type: 'integer'
}
}
},
str: {
type: 'string'
},
def: {
type: 'null'
}
},
type: 'object',
properties: {
nickname: {
$ref: '#/definitions/str'
}
},
patternProperties: {
num: {
$ref: '#/definitions/num'
}
},
additionalProperties: {
$ref: '#/definitions/def'
}
})({ nickname: '', num: { int: 5 }, other: null })
// Conditional/Combined schemas
build({
title: 'Conditional/Combined Schema',
type: 'object',
properties: {
something: {
anyOf: [
{ type: 'string' },
{ type: 'boolean' }
]
}
},
if: {
properties: {
something: { type: 'string' }
}
},
then: {
properties: {
somethingElse: { type: 'number' }
}
},
else: {
properties: {
somethingElse: { type: 'null' }
}
}
})({ something: 'a string', somethingElse: 42 })
// String schema with format
build({
type: 'string',
format: 'date-time'
})(new Date())
/*
This overload doesn't work yet -
TypeScript chooses the generic for the schema
before it chooses the overload for the options
parameter.
let str: string, ajv: Ajv
str = build({
type: 'number'
}, { debugMode: true }).code
ajv = build({
type: 'number'
}, { debugMode: true }).ajv
str = build({
type: 'number'
}, { mode: 'debug' }).code
ajv = build({
type: 'number'
}, { mode: 'debug' }).ajv
str = build({
type: 'number'
}, { mode: 'standalone' })
*/
const debugCompiled = build({
title: 'default string',
type: 'object',
properties: {
firstName: {
type: 'string'
}
}
}, { mode: 'debug' })
expect(build.restore(debugCompiled)).type.toBe(build({} as Schema))
expect(restore(debugCompiled)).type.toBe(build({} as Schema))
expect(build.validLargeArrayMechanisms).type.toBe<string[]>()
expect(validLargeArrayMechanisms).type.toBe<string[]>()
/**
* Schema inference
*/
// With inference
interface InferenceSchema {
id: string;
a?: number;
}
const stringify3 = build({
type: 'object',
properties: { a: { type: 'string' } },
})
stringify3<InferenceSchema>({ id: '123' })
stringify3<InferenceSchema>({ a: 123, id: '123' })
expect(stringify3<InferenceSchema>).type.not.toBeCallableWith({ anotherOne: 'bar' })
expect(stringify3<Schema>).type.not.toBeCallableWith({ a: 'bar' })
// Without inference
const stringify4 = build({
type: 'object',
properties: { a: { type: 'string' } },
})
stringify4({ id: '123' })
stringify4({ a: 123, id: '123' })
stringify4({ anotherOne: 'bar' })
stringify4({ a: 'bar' })
// Without inference - string type
const stringify5 = build({
type: 'string',
})
stringify5('foo')
expect(stringify5).type.not.toBeCallableWith({ id: '123' })
// Without inference - null type
const stringify6 = build({
type: 'null',
})
stringify6(null)
expect(stringify6).type.not.toBeCallableWith('a string')
// Without inference - boolean type
const stringify7 = build({
type: 'boolean',
})
stringify7(true)
expect(stringify7).type.not.toBeCallableWith('a string')
// largeArrayMechanism
build({}, { largeArrayMechanism: 'json-stringify' })
build({}, { largeArrayMechanism: 'default' })
expect(build).type.not.toBeCallableWith({} as Schema, { largeArrayMechanism: 'invalid' })
build({}, { largeArraySize: 2000 })
build({}, { largeArraySize: '2e4' })
build({}, { largeArraySize: 2n })
expect(build).type.not.toBeCallableWith({} as Schema, { largeArraySize: ['asdf'] })
+1
-1

@@ -30,5 +30,5 @@ name: CI

pull-requests: write
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v6
with:
license-check: true
lint: true

@@ -172,4 +172,2 @@ 'use strict'

Number(str)
for (let i = 0; i < MULTI_ARRAY_LENGTH; i++) {

@@ -179,132 +177,132 @@ multiArray[i] = obj

suite.add('FJS creation', function () {
suite.add('fast-json-stringify: creation', function () {
FJS(schema)
})
suite.add('CJS creation', function () {
suite.add('compile-json-stringify: creation', function () {
CJS(schemaCJS)
})
suite.add('AJV Serialize creation', function () {
suite.add('AJV: creation', function () {
ajv.compileSerializer(schemaAJVJTD)
})
suite.add('json-accelerator creation', function () {
suite.add('json-accelerator: creation', function () {
createAccelerator(schema)
})
suite.add('JSON.stringify array', function () {
suite.add('JSON.stringify: array', function () {
JSON.stringify(multiArray)
})
suite.add('fast-json-stringify array default', function () {
suite.add('fast-json-stringify [default]: array', function () {
stringifyArrayDefault(multiArray)
})
suite.add('json-accelerator array', function () {
suite.add('json-accelerator: array', function () {
accelArray(multiArray)
})
suite.add('fast-json-stringify array json-stringify', function () {
suite.add('fast-json-stringify [json-stringify]: array', function () {
stringifyArrayJSONStringify(multiArray)
})
suite.add('compile-json-stringify array', function () {
suite.add('compile-json-stringify: array', function () {
CJSStringifyArray(multiArray)
})
suite.add('AJV Serialize array', function () {
suite.add('AJV: array', function () {
ajvSerializeArray(multiArray)
})
suite.add('JSON.stringify large array', function () {
suite.add('JSON.stringify: large array', function () {
JSON.stringify(largeArray)
})
suite.add('fast-json-stringify large array default', function () {
suite.add('fast-json-stringify [default]: large array', function () {
stringifyArrayDefault(largeArray)
})
suite.add('fast-json-stringify large array json-stringify', function () {
suite.add('fast-json-stringify [json-stringify]: large array', function () {
stringifyArrayJSONStringify(largeArray)
})
suite.add('compile-json-stringify large array', function () {
suite.add('compile-json-stringify: large array', function () {
CJSStringifyArray(largeArray)
})
suite.add('AJV Serialize large array', function () {
suite.add('AJV: large array', function () {
ajvSerializeArray(largeArray)
})
suite.add('JSON.stringify long string', function () {
suite.add('JSON.stringify: long string', function () {
JSON.stringify(str)
})
suite.add('fast-json-stringify long string', function () {
suite.add('fast-json-stringify: long string', function () {
stringifyString(str)
})
suite.add('json-accelerator long string', function () {
suite.add('json-accelerator: long string', function () {
stringifyString(str)
})
suite.add('compile-json-stringify long string', function () {
suite.add('compile-json-stringify: long string', function () {
CJSStringifyString(str)
})
suite.add('AJV Serialize long string', function () {
suite.add('AJV: long string', function () {
ajvSerializeString(str)
})
suite.add('JSON.stringify short string', function () {
suite.add('JSON.stringify: short string', function () {
JSON.stringify('hello world')
})
suite.add('fast-json-stringify short string', function () {
suite.add('fast-json-stringify: short string', function () {
stringifyString('hello world')
})
suite.add('json-accelerator short string', function () {
suite.add('json-accelerator: short string', function () {
accelString('hello world')
})
suite.add('compile-json-stringify short string', function () {
suite.add('compile-json-stringify: short string', function () {
CJSStringifyString('hello world')
})
suite.add('AJV Serialize short string', function () {
suite.add('AJV: short string', function () {
ajvSerializeString('hello world')
})
suite.add('JSON.stringify obj', function () {
suite.add('JSON.stringify: obj', function () {
JSON.stringify(obj)
})
suite.add('fast-json-stringify obj', function () {
suite.add('fast-json-stringify: obj', function () {
stringify(obj)
})
suite.add('json-accelerator obj', function () {
suite.add('json-accelerator: obj', function () {
accelStringify(obj)
})
suite.add('compile-json-stringify obj', function () {
suite.add('compile-json-stringify: obj', function () {
CJSStringify(obj)
})
suite.add('AJV Serialize obj', function () {
suite.add('AJV: obj', function () {
ajvSerialize(obj)
})
suite.add('JSON stringify date', function () {
suite.add('JSON.stringify: date', function () {
JSON.stringify(date)
})
suite.add('fast-json-stringify date format', function () {
suite.add('fast-json-stringify: date', function () {
stringifyDate(date)
})
suite.add('json-accelerate date format', function () {
suite.add('json-accelerate: date', function () {
accelDate(date)
})
suite.add('compile-json-stringify date format', function () {
suite.add('compile-json-stringify: date', function () {
CJSStringifyDate(date)

@@ -314,13 +312,31 @@ })

suite.run().then(() => {
for (const task of suite.tasks) {
const hz = task.result.throughput.mean // ops/sec
const rme = task.result.latency.rme // relative margin of error (%)
const samples = task.result.latency.df + 1 // degrees of freedom + 1 = sample count
const results = suite.tasks.map(task => ({
name: task.name,
hz: task.result.throughput.mean,
rme: task.result.latency.rme,
samples: task.result.latency.df + 1
}))
const formattedHz = hz.toLocaleString('en-US', { maximumFractionDigits: 0 })
const formattedRme = rme.toFixed(2)
const scenarios = {}
for (const result of results) {
const [library, scenario] = result.name.split(':').map(s => s.trim())
const output = `${task.name} x ${formattedHz} ops/sec ±${formattedRme}% (${samples} runs sampled)`
console.log(output)
if (!scenarios[scenario]) scenarios[scenario] = []
scenarios[scenario].push({ ...result, library })
}
for (const [scenario, tasks] of Object.entries(scenarios)) {
console.log(`\n--- ${scenario} ---`)
const sorted = tasks.sort((a, b) => b.hz - a.hz)
const winner = sorted[0]
for (const task of sorted) {
const formattedHz = task.hz.toLocaleString('en-US', { maximumFractionDigits: 0 })
const formattedRme = task.rme.toFixed(2)
const isWinner = task === winner
const prefix = isWinner ? '🏆 ' : ' '
console.log(`${prefix}${task.library.padEnd(40)} x ${formattedHz.padStart(15)} ops/sec ±${formattedRme}% (${task.samples} runs sampled)`)
}
}
}).catch(err => console.error(`Error: ${err.message}`))

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

if (context.recursivePaths.has(fullPath) || context.buildingSet.has(schema)) {
if (context.recursivePaths.has(fullPath) || context.buildingSet.has(schema) || schemaId !== '') {
const functionName = generateFuncName(context)

@@ -631,3 +631,3 @@ context.functionsNamesBySchema.set(schema, functionName)

if (context.recursivePaths.has(fullPath) || context.buildingSet.has(schema)) {
if (context.recursivePaths.has(fullPath) || context.buildingSet.has(schema) || schemaId !== '') {
const functionName = generateFuncName(context)

@@ -634,0 +634,0 @@ context.functionsNamesBySchema.set(schema, functionName)

MIT License
Copyright (c) 2016-present Matteo Collina
Copyright (c) 2016-present The Fastify team
Copyright (c) 2016-present The Fastify team <https://github.com/fastify/fastify#team>
The Fastify team members are listed at https://github.com/fastify/fastify#team.
Permission is hereby granted, free of charge, to any person obtaining a copy

@@ -9,0 +7,0 @@ of this software and associated documentation files (the "Software"), to deal

{
"name": "fast-json-stringify",
"version": "6.3.0",
"version": "6.4.0",
"description": "Stringify your JSON at max speed",

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

"lint:fix": "eslint --fix",
"test:typescript": "tsd",
"test:typescript": "tstyche",
"test:unit": "c8 node --test",

@@ -66,4 +66,3 @@ "test": "npm run test:unit && npm run test:typescript"

"devDependencies": {
"@sinclair/typebox": "^0.34.48",
"c8": "^10.1.3",
"c8": "^11.0.0",
"cli-select": "^1.1.2",

@@ -75,7 +74,8 @@ "compile-json-stringify": "^0.1.2",

"json-accelerator": "^0.1.7",
"neostandard": "^0.12.2",
"neostandard": "^0.13.0",
"simple-git": "^3.30.0",
"tinybench": "^6.0.0",
"tsd": "^0.33.0",
"typescript": "^5.9.3",
"tstyche": "^7.1.0",
"typebox": "^1.0.81",
"typescript": "~6.0.2",
"webpack": "^5.104.1"

@@ -82,0 +82,0 @@ },

@@ -793,1 +793,21 @@ 'use strict'

})
test('invalid anyOf schema', (t) => {
t.plan(1)
const schema = {
type: 'object',
properties: {
prop: {
anyOf: 'not array' // invalid, anyOf must be array
}
}
}
try {
build(schema)
t.assert.fail('Should throw')
} catch (err) {
t.assert.ok(err.message.includes('schema is invalid'))
}
})

@@ -389,2 +389,24 @@ 'use strict'

test('array items is a list of schema and additionalItems is a schema', (t) => {
t.plan(1)
const schema = {
type: 'object',
properties: {
foo: {
type: 'array',
items: [
{ type: 'string' }
],
additionalItems: { type: 'number' }
}
}
}
const stringify = build(schema)
const result = stringify({ foo: ['foo', 42] })
t.assert.equal(result, '{"foo":["foo",42]}')
})
// https://github.com/fastify/fast-json-stringify/issues/279

@@ -391,0 +413,0 @@ test('object array with anyOf and symbol', (t) => {

@@ -122,1 +122,22 @@ 'use strict'

})
test('Serializer restoreFromState', t => {
t.plan(1)
const Serializer = require('../lib/serializer')
const serializer = new Serializer()
const state = serializer.getState()
const restored = Serializer.restoreFromState(state)
t.assert.ok(restored instanceof Serializer)
})
test('Validator restoreFromState', t => {
t.plan(1)
const Validator = require('../lib/validator')
const validator = new Validator()
validator.addSchema({ type: 'string' }, 'test')
const state = validator.getState()
const restored = Validator.restoreFromState(state)
t.assert.ok(restored instanceof Validator)
})

@@ -469,1 +469,73 @@ 'use strict'

})
test('invalid if-then-else schema', (t) => {
t.plan(1)
const schema = {
type: 'object',
if: {
type: 'object',
properties: {
kind: { type: 'string', enum: ['foobar'] }
}
},
then: {
type: 'object',
properties: {
foo: { type: 'string' }
}
},
else: {
type: 'object',
properties: 'invalid' // properties must be object
}
}
try {
build(schema)
t.assert.fail('Should throw')
} catch (err) {
t.assert.ok(err.message.includes('schema is invalid'))
}
})
test('if-then-else with allOf', (t) => {
t.plan(2)
const schema = {
type: 'object',
allOf: [
{
type: 'object',
properties: {
base: { type: 'string' }
}
}
],
if: {
type: 'object',
properties: {
kind: { type: 'string', enum: ['foobar'] }
}
},
then: {
type: 'object',
properties: {
foo: { type: 'string' }
}
},
else: {
type: 'object',
properties: {
bar: { type: 'string' }
}
}
}
const stringify = build(schema)
const data = { base: 'test', kind: 'foobar', foo: 'value' }
const output = stringify(data)
t.assert.doesNotThrow(() => JSON.parse(output))
t.assert.equal(output, '{"base":"test","foo":"value"}')
})

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

})
test('invalid not schema', (t) => {
t.plan(1)
const schema = {
type: 'object',
properties: {
prop: {
not: 'not object' // invalid, not must be object
}
}
}
try {
build(schema)
t.assert.fail('Should throw')
} catch (err) {
t.assert.ok(err.message.includes('schema is invalid'))
}
})

@@ -491,1 +491,21 @@ 'use strict'

})
test('invalid oneOf schema', (t) => {
t.plan(1)
const schema = {
type: 'object',
properties: {
prop: {
oneOf: 'not array' // invalid, oneOf must be array
}
}
}
try {
build(schema)
t.assert.fail('Should throw')
} catch (err) {
t.assert.ok(err.message.includes('schema is invalid'))
}
})

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

})
test('ref nested', (t) => {
t.plan(2)
const schema = {
definitions: {
def1: {
$ref: '#/definitions/def2'
},
def2: {
type: 'string'
}
},
type: 'object',
properties: {
str: {
$ref: '#/definitions/def1'
}
}
}
const object = {
str: 'test'
}
const stringify = build(schema)
const output = stringify(object)
t.assert.doesNotThrow(() => JSON.parse(output))
t.assert.equal(output, '{"str":"test"}')
})

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

test('nested object in pattern properties for typebox', (t) => {
const { Type } = require('@sinclair/typebox')
const { Type } = require('typebox')

@@ -10,0 +10,0 @@ t.plan(1)

# Number of days of inactivity before an issue becomes stale
daysUntilStale: 15
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- "discussion"
- "feature request"
- "bug"
- "help wanted"
- "plugin suggestion"
- "good first issue"
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Test using this disabled, see https://github.com/fastify/fast-json-stringify/pull/683
import Ajv from 'ajv'
import build, { restore, Schema, validLargeArrayMechanisms } from '..'
import { expectError, expectType } from 'tsd'
// Number schemas
build({
type: 'number'
})(25)
build({
type: 'integer'
})(-5)
build({
type: 'integer'
})(5n)
build({
type: 'number'
}, { rounding: 'ceil' })
build({
type: 'number'
}, { rounding: 'floor' })
build({
type: 'number'
}, { rounding: 'round' })
build({
type: 'number'
}, { rounding: 'trunc' })
expectError(build({
type: 'number'
}, { rounding: 'invalid' }))
// String schema
build({
type: 'string'
})('foobar')
// Boolean schema
build({
type: 'boolean'
})(true)
// Null schema
build({
type: 'null'
})(null)
// Array schemas
build({
type: 'array',
items: { type: 'number' }
})([25])
build({
type: 'array',
items: [{ type: 'string' }, { type: 'integer' }]
})(['hello', 42])
// Object schemas
build({
type: 'object'
})({})
build({
type: 'object',
properties: {
foo: { type: 'string' },
bar: { type: 'integer' }
},
required: ['foo'],
patternProperties: {
'baz*': { type: 'null' }
},
additionalProperties: {
type: 'boolean'
}
})({ foo: 'bar' })
build({
type: 'object',
properties: {
foo: { type: 'string' },
bar: { type: 'integer' }
},
required: ['foo'],
patternProperties: {
'baz*': { type: 'null' }
},
additionalProperties: {
type: 'boolean'
}
}, { rounding: 'floor' })({ foo: 'bar' })
// Reference schemas
build({
title: 'Example Schema',
definitions: {
num: {
type: 'object',
properties: {
int: {
type: 'integer'
}
}
},
str: {
type: 'string'
},
def: {
type: 'null'
}
},
type: 'object',
properties: {
nickname: {
$ref: '#/definitions/str'
}
},
patternProperties: {
num: {
$ref: '#/definitions/num'
}
},
additionalProperties: {
$ref: '#/definitions/def'
}
})({ nickname: '', num: { int: 5 }, other: null })
// Conditional/Combined schemas
build({
title: 'Conditional/Combined Schema',
type: 'object',
properties: {
something: {
anyOf: [
{ type: 'string' },
{ type: 'boolean' }
]
}
},
if: {
properties: {
something: { type: 'string' }
}
},
then: {
properties: {
somethingElse: { type: 'number' }
}
},
else: {
properties: {
somethingElse: { type: 'null' }
}
}
})({ something: 'a string', somethingElse: 42 })
// String schema with format
build({
type: 'string',
format: 'date-time'
})(new Date())
/*
This overload doesn't work yet -
TypeScript chooses the generic for the schema
before it chooses the overload for the options
parameter.
let str: string, ajv: Ajv
str = build({
type: 'number'
}, { debugMode: true }).code
ajv = build({
type: 'number'
}, { debugMode: true }).ajv
str = build({
type: 'number'
}, { mode: 'debug' }).code
ajv = build({
type: 'number'
}, { mode: 'debug' }).ajv
str = build({
type: 'number'
}, { mode: 'standalone' })
*/
const debugCompiled = build({
title: 'default string',
type: 'object',
properties: {
firstName: {
type: 'string'
}
}
}, { mode: 'debug' })
expectType<ReturnType<typeof build>>(build.restore(debugCompiled))
expectType<ReturnType<typeof build>>(restore(debugCompiled))
expectType<string[]>(build.validLargeArrayMechanisms)
expectType<string[]>(validLargeArrayMechanisms)
/**
* Schema inference
*/
// With inference
interface InferenceSchema {
id: string;
a?: number;
}
const stringify3 = build({
type: 'object',
properties: { a: { type: 'string' } },
})
stringify3<InferenceSchema>({ id: '123' })
stringify3<InferenceSchema>({ a: 123, id: '123' })
expectError(stringify3<InferenceSchema>({ anotherOne: 'bar' }))
expectError(stringify3<Schema>({ a: 'bar' }))
// Without inference
const stringify4 = build({
type: 'object',
properties: { a: { type: 'string' } },
})
stringify4({ id: '123' })
stringify4({ a: 123, id: '123' })
stringify4({ anotherOne: 'bar' })
stringify4({ a: 'bar' })
// Without inference - string type
const stringify5 = build({
type: 'string',
})
stringify5('foo')
expectError(stringify5({ id: '123' }))
// Without inference - null type
const stringify6 = build({
type: 'null',
})
stringify6(null)
expectError(stringify6('a string'))
// Without inference - boolean type
const stringify7 = build({
type: 'boolean',
})
stringify7(true)
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'] }))