fast-json-stringify
fast-json-stringify is significantly faster than JSON.stringify() for small payloads.
Its performance advantage shrinks as your payload grows.
How it works
fast-json-stringify requires a JSON Schema Draft 7 input to generate a fast stringify function.
Benchmarks
- Machine:
EX41S-SSD, Intel Core i7, 4Ghz, 64GB RAM, 4C/8T, SSD. - Node.js
v22.14.0
FJS creation x 9,696 ops/sec ±0.77% (94 runs sampled)
CJS creation x 197,267 ops/sec ±0.22% (95 runs sampled)
AJV Serialize creation x 48,302,927 ops/sec ±2.09% (90 runs sampled)
json-accelerator creation x 668,430 ops/sec ±0.43% (95 runs sampled)
JSON.stringify array x 7,924 ops/sec ±0.11% (98 runs sampled)
fast-json-stringify array default x 7,183 ops/sec ±0.09% (97 runs sampled)
json-accelerator array x 5,762 ops/sec ±0.27% (99 runs sampled)
fast-json-stringify array json-stringify x 7,171 ops/sec ±0.17% (97 runs sampled)
compile-json-stringify array x 6,889 ops/sec ±0.41% (96 runs sampled)
AJV Serialize array x 6,945 ops/sec ±0.17% (98 runs sampled)
JSON.stringify large array x 331 ops/sec ±0.17% (93 runs sampled)
fast-json-stringify large array default x 208 ops/sec ±0.21% (91 runs sampled)
fast-json-stringify large array json-stringify x 330 ops/sec ±0.17% (93 runs sampled)
compile-json-stringify large array x 318 ops/sec ±0.11% (90 runs sampled)
AJV Serialize large array x 114 ops/sec ±0.27% (74 runs sampled)
JSON.stringify long string x 13,452 ops/sec ±0.15% (99 runs sampled)
fast-json-stringify long string x 13,454 ops/sec ±0.10% (99 runs sampled)
json-accelerator long string x 13,439 ops/sec ±0.09% (98 runs sampled)
compile-json-stringify long string x 13,380 ops/sec ±0.12% (100 runs sampled)
AJV Serialize long string x 21,932 ops/sec ±0.06% (99 runs sampled)
JSON.stringify short string x 12,114,052 ops/sec ±0.59% (97 runs sampled)
fast-json-stringify short string x 29,408,175 ops/sec ±1.12% (91 runs sampled)
json-accelerator short string x 29,431,694 ops/sec ±1.05% (93 runs sampled)
compile-json-stringify short string x 24,740,338 ops/sec ±1.02% (91 runs sampled)
AJV Serialize short string x 17,841,869 ops/sec ±0.90% (91 runs sampled)
JSON.stringify obj x 4,577,494 ops/sec ±0.25% (94 runs sampled)
fast-json-stringify obj x 7,291,157 ops/sec ±0.40% (97 runs sampled)
json-accelerator obj x 6,473,194 ops/sec ±0.31% (99 runs sampled)
compile-json-stringify obj x 14,724,935 ops/sec ±0.50% (96 runs sampled)
AJV Serialize obj x 8,782,944 ops/sec ±0.45% (93 runs sampled)
JSON stringify date x 803,522 ops/sec ±0.47% (98 runs sampled)
fast-json-stringify date format x 1,117,776 ops/sec ±0.69% (95 runs sampled)
json-accelerate date format x 1,122,419 ops/sec ±0.20% (97 runs sampled)
compile-json-stringify date format x 803,214 ops/sec ±0.23% (97 runs sampled)
Table of contents:
ExampleOptionsAPIfastJsonStringifySpecific use casesRequiredMissing fieldsPattern PropertiesAdditional PropertiesAnyOfandOneOfReuse - $refLong integersIntegersNullableLarge ArraysSecurity NoticeDebug ModeStandalone ModeAcknowledgmentsLicense
Example
const fastJson = require('fast-json-stringify')
const stringify = fastJson({
title: 'Example Schema',
type: 'object',
properties: {
firstName: {
type: 'string'
},
lastName: {
type: 'string'
},
age: {
description: 'Age in years',
type: 'integer'
},
reg: {
type: 'string'
}
}
})
console.log(stringify({
firstName: 'Matteo',
lastName: 'Collina',
age: 32,
reg: /"([^"]|\\")*"/
}))
Options
Optionally, you may provide to fast-json-stringify an option object as the second parameter:
const fastJson = require('fast-json-stringify')
const stringify = fastJson(mySchema, {
schema: { ... },
ajv: { ... },
rounding: 'ceil',
maxDepth: 100
})
schema: external schemas references by $ref property. More detailsajv: ajv v8 instance's settings for those properties that requireajv. More details Schema strictness defaults tofalse; passajv: { strictSchema: true }to reject unknown schema keywords orajv: { strictSchema: 'log' }to log warnings. This applies only to schema paths compiled by Ajv, not to every schema processed by fast-json-stringify.rounding: setup how theintegertypes will be rounded when not integers. More detailsmaxDepth: maximum number of nested schema levels allowed during compilation. Defaults to100and may be set from0to100. Schemas that exceed this limit are rejected before compilation.inlineValidators: when using standalone mode, embed Ajv-generated validator functions in the output instead of compiling schemas at runtime. More detailslargeArrayMechanism: set the mechanism that should be used to handle large (by default20000or more items) arrays. More detailsobjectMechanism: set the mechanism used to serialize objects.'native'projects the input into schema-shaped scratch objects and delegates to the nativeJSON.stringify, which is much faster on Node.js 25+. More detailscompileValidators: whentrue, theajvvalidators used byanyOf,oneOfandif/then/elseare compiled duringbuild()instead of lazily, on the first serialization that reaches them. This makesbuild()slower but removes a potentially large one-off cost from the firststringify()call. Defaults tofalse.
API
fastJsonStringify(schema)
Build a stringify() function based on jsonschema draft 7 spec.
Supported types:
'string''integer''number''array''object''boolean''null'
And nested ones, too.
Specific use cases
| Instance | Serialized as |
|---|---|
Date |
string via toISOString() |
RegExp |
string |
BigInt |
integer via toString |
JSON Schema built-in formats for dates are supported and will be serialized as:
| Format | Serialized format example |
|---|---|
date-time |
2020-04-03T09:11:08.615Z |
date |
2020-04-03 |
time |
09:11:08 |
Note: In the case of a string formatted Date and not Date Object, the value is not reformatted nor validated: it should already be properly formatted. It is only escaped, so that the resulting document is always valid JSON.
Example with a Date object:
const stringify = fastJson({
title: 'Example Schema with string date-time field',
type: 'string',
format: 'date-time'
})
const date = new Date()
console.log(stringify(date)) // '"YYYY-MM-DDTHH:mm:ss.sssZ"'
Required
You can set specific fields of an object as required in your schema by adding the field name inside the required array in your schema.
Example:
const schema = {
title: 'Example Schema with required field',
type: 'object',
properties: {
nickname: {
type: 'string'
},
mail: {
type: 'string'
}
},
required: ['mail']
}
If the object to stringify is missing the required field(s), fast-json-stringify will throw an error.
Missing fields
If a field is present in the schema (and is not required) but it is not present in the object to stringify, fast-json-stringify will not write it in the final string.
Example:
const stringify = fastJson({
title: 'Example Schema',
type: 'object',
properties: {
nickname: {
type: 'string'
},
mail: {
type: 'string'
}
}
})
const obj = {
mail: 'mail@example.com'
}
console.log(stringify(obj)) // '{"mail":"mail@example.com"}'
Defaults
fast-json-stringify supports default jsonschema key in order to serialize a value
if it is undefined or not present.
Example:
const stringify = fastJson({
title: 'Example Schema',
type: 'object',
properties: {
nickname: {
type: 'string',
default: 'the default string'
}
}
})
console.log(stringify({})) // '{"nickname":"the default string"}'
console.log(stringify({nickname: 'my-nickname'})) // '{"nickname":"my-nickname"}'
Pattern properties
fast-json-stringify supports pattern properties as defined by JSON schema.
patternProperties must be an object, where the key is a valid regex and the value is an object, declared in this way: { type: 'type' }.
patternProperties will work only for the properties that are not explicitly listed in the properties object.
Example:
const stringify = fastJson({
title: 'Example Schema',
type: 'object',
properties: {
nickname: {
type: 'string'
}
},
patternProperties: {
'num': {
type: 'number'
},
'.*foo
Additional properties
fast-json-stringify supports additional properties as defined by JSON schema.
additionalProperties must be an object or a boolean, declared in this way: { type: 'type' }.
additionalProperties will work only for the properties that are not explicitly listed in the properties and patternProperties objects.
If additionalProperties is not present or is set to false, every property that is not explicitly listed in the properties and patternProperties objects will be ignored, as described in Missing fields.
Missing fields are ignored to avoid having to rewrite objects before serializing. However, other schema rules would throw in similar situations.
If additionalProperties is set to true, it will be used by JSON.stringify to stringify the additional properties. If you want to achieve maximum performance, we strongly encourage you to use a fixed schema where possible.
The additional properties will always be serialized at the end of the object.
Example:
const stringify = fastJson({
title: 'Example Schema',
type: 'object',
properties: {
nickname: {
type: 'string'
}
},
patternProperties: {
'num': {
type: 'number'
},
'.*foo
AnyOf and OneOf
fast-json-stringify supports the anyOf and oneOf keywords as defined by JSON schema. Both must be an array of valid JSON schemas. The different schemas will be tested in the specified order. The more schemas stringify has to try before finding a match, the slower it will be.
anyOf and oneOf use ajv as a JSON schema validator to find the schema that matches the data. This has an impact on performance—only use it as a last resort.
Example:
const stringify = fastJson({
title: 'Example Schema',
type: 'object',
properties: {
'undecidedType': {
'anyOf': [{
type: 'string'
}, {
type: 'boolean'
}]
}
}
})
When specifying object JSON schemas for anyOf, add required validation keyword to match only the objects with the properties you want.
Example:
const stringify = fastJson({
title: 'Example Schema',
type: 'array',
items: {
anyOf: [
{
type: 'object',
properties: {
savedId: { type: 'string' }
},
// without "required" validation any object will match
required: ['savedId']
},
{
type: 'object',
properties: {
error: { type: 'string' }
},
required: ['error']
}
]
}
})
If/then/else
fast-json-stringify supports if/then/else jsonschema feature. See ajv documentation.
Example:
const stringify = fastJson({
'type': 'object',
'properties': {
},
'if': {
'properties': {
'kind': { 'type': 'string', 'enum': ['foobar'] }
}
},
'then': {
'properties': {
'kind': { 'type': 'string', 'enum': ['foobar'] },
'foo': { 'type': 'string' },
'bar': { 'type': 'number' }
}
},
'else': {
'properties': {
'kind': { 'type': 'string', 'enum': ['greeting'] },
'hi': { 'type': 'string' },
'hello': { 'type': 'number' }
}
}
})
console.log(stringify({
kind: 'greeting',
foo: 'FOO',
bar: 42,
hi: 'HI',
hello: 45
})) // {"kind":"greeting","hi":"HI","hello":45}
console.log(stringify({
kind: 'foobar',
foo: 'FOO',
bar: 42,
hi: 'HI',
hello: 45
})) // {"kind":"foobar","foo":"FOO","bar":42}
NB Do not declare the properties twice or you will print them twice!
Reuse - $ref
If you want to reuse a definition of a value, you can use the property $ref.
The value of $ref must be a string in JSON Pointer format.
Example:
const schema = {
title: 'Example Schema',
definitions: {
num: {
type: 'object',
properties: {
int: {
type: 'integer'
}
}
},
str: {
type: 'string'
}
},
type: 'object',
properties: {
nickname: {
$ref: '#/definitions/str'
}
},
patternProperties: {
'num': {
$ref: '#/definitions/num'
}
},
additionalProperties: {
$ref: '#/definitions/def'
}
}
const stringify = fastJson(schema)
If you need to use an external definition, you can pass it as an option to fast-json-stringify.
Example:
const schema = {
title: 'Example Schema',
type: 'object',
properties: {
nickname: {
$ref: 'strings#/definitions/str'
}
},
patternProperties: {
'num': {
$ref: 'numbers#/definitions/num'
}
},
additionalProperties: {
$ref: 'strings#/definitions/def'
}
}
const externalSchema = {
numbers: {
definitions: {
num: {
type: 'object',
properties: {
int: {
type: 'integer'
}
}
}
}
},
strings: require('./string-def.json')
}
const stringify = fastJson(schema, { schema: externalSchema })
External definitions can also reference each other.
Example:
const schema = {
title: 'Example Schema',
type: 'object',
properties: {
foo: {
$ref: 'strings#/definitions/foo'
}
}
}
const externalSchema = {
strings: {
definitions: {
foo: {
$ref: 'things#/definitions/foo'
}
}
},
things: {
definitions: {
foo: {
type: 'string'
}
}
}
}
const stringify = fastJson(schema, { schema: externalSchema })
Long integers
By default, the library will handle automatically BigInt.
Integers
The type: integer property will be truncated if a floating point is provided.
You can customize this behavior with the rounding option that will accept round, ceil, floor, or trunc. Default is trunc:
const stringify = fastJson(schema, { rounding: 'ceil' })
Nullable
According to the Open API 3.0 specification, a value that can be null must be declared nullable.
Nullable object
const stringify = fastJson({
'title': 'Nullable schema',
'type': 'object',
'nullable': true,
'properties': {
'product': {
'nullable': true,
'type': 'object',
'properties': {
'name': {
'type': 'string'
}
}
}
}
})
console.log(stringify({product: {name: "hello"}})) // "{"product":{"name":"hello"}}"
console.log(stringify({product: null})) // "{"product":null}"
console.log(stringify(null)) // null
Otherwise, instead of raising an error, null values will be coerced as follows:
integer -> 0
number -> 0
string -> ""
boolean -> false
object -> {}
array -> []
Large Arrays
Large arrays are, for the scope of this document, defined as arrays containing,
by default, 20000 elements or more. That value can be adjusted via the option
parameter largeArraySize.
At some point the overhead caused by the default mechanism used by
fast-json-stringify to handle arrays starts increasing exponentially, leading
to slow overall executions.
Settings
In order to improve that the user can set the largeArrayMechanism and
largeArraySize options.
largeArrayMechanism's default value is default. Valid values for it are:
default - This option keeps the full feature set of this lib: every element
is still coerced to the type declared in items, and type errors are still
thrown. Arrays of plain primitives (string, number, integer) use a
copy-then-JSON.stringify strategy with their own length cutoffs, see
Primitive arrays below; largeArraySize does not affect
them.
json-stringify - This option will remove support for schema validation
within large arrays completely. By doing so the overhead previously
mentioned is nulled, greatly improving execution time. Mind there's no change
in behavior for arrays not considered large
largeArraySize's default value is 20000. Valid values for it are
integer-like values, such as:
20000
2e4
'20000'
'2e4' - note this will be converted to 2, not 20000
1.5 - note this will be converted to 1
Primitive arrays
With largeArrayMechanism: 'default', arrays whose items schema is a plain
{ type: 'string' }, { type: 'number' } or { type: 'integer' } (no
format, nullable, const, or combinator keyword; a $ref to a plain
string schema also qualifies, a $ref for number/integer does not) are
serialized by copying the elements into a fresh array and calling the native
JSON.stringify on the copy, which is several times faster than appending one
element at a time. This happens at or above a length cutoff that does not
depend on largeArraySize. The cutoffs are the array lengths above which the
copy beats the per-element loop, and they depend on the V8 version:
Node.js
string
number
integer
22 and later
8 elements
128 elements
512 elements
20
128 elements
300000 elements
300000 elements
Below the cutoff the array is serialized element by element as before. Above
it, the elements are copied until one is found that is not already of the
declared type (not a string, not a number, or not an integer for integer);
that element and the ones after it go through the regular per-element coercion
and validation (null still becomes "" or 0, rounding still applies,
NaN still throws). The output is byte-identical either way. Arrays of
boolean, null, objects, or any other items schema are not affected.
In standalone mode the cutoffs are baked into the generated
code, so the generated module inherits the cutoffs of the Node.js version that
ran build(), not of the one that runs the module.
Object mechanism
By default every object is serialized by generated code that appends the JSON
text property by property. Since V8 13.8 (Node.js 25+) the native
JSON.stringify has a side-effect-free fast path that no JavaScript string
building can match, but that fast path bails out as soon as the input carries a
Date, a toJSON, a class prototype or any property that is not plain data.
With objectMechanism: 'native', fast-json-stringify instead projects the
input into preallocated scratch objects whose keys are exactly the schema
properties, in schema order, and hands the scratch to the native
JSON.stringify. The scratch is plain by construction, so the fast path is
always taken, whatever the input looks like, while the schema is still applied:
undeclared properties are dropped, required is enforced, default applied,
types coerced and format: date-time / date / time rendered as before.
The output is byte-identical to the default mechanism.
const stringify = fastJson(schema, { objectMechanism: 'native' })
The projection covers schemas made only of:
object with properties (no additionalProperties, patternProperties,
nullable, dependencies, combinators or $ref), with at least one property;
string (optionally with format: date-time, date or time), number,
integer, boolean, without nullable, const, enum, combinators or $ref;
- the two-element forms
['string', 'null'], ['number', 'null'] and
['boolean', 'null'] (in either order), with the same strict type dispatch
as the default mechanism;
array whose items is one such schema (no tuples), nested to any depth;
every array element gets its own scratch objects.
Any other schema silently uses the default mechanism, so the option is always
safe to enable. The projection only pays off where V8 has the fast path, so on
Node.js 24 and older the option is a no-op and the default mechanism is used.
On Node.js 25+ the projection serializes plain objects within 5-15% of a bare
JSON.stringify (the default mechanism is 35-50% behind it there), and objects
coming from an ORM (extra columns, Date instances, class prototypes) several
times faster than both the default mechanism and JSON.stringify itself.
The scratch objects are reused across calls; JSON.stringify is synchronous,
so this is safe, and no reference to the last input is retained beyond the
primitive values written into the scratch.
Unsafe string
By default, the library escapes all strings. With the 'unsafe' format, the string isn't escaped. This has a potentially dangerous security issue. You can use it only if you are sure that your data doesn't need escaping. The advantage is a significant performance improvement.
Example:
const stringify = fastJson({
title: 'Example Schema',
type: 'object',
properties: {
'code': {
type: 'string',
format: 'unsafe'
}
}
})
Benchmarks
For reference, here are some benchmarks for comparison over the three
mechanisms. Benchmarks were conducted on an old machine.
- Machine:
ST1000LM024 HN-M 1TB HDD, Intel Core i7-3610QM @ 2.3GHz, 12GB RAM, 4C/8T.
- Node.js
v16.13.1
JSON.stringify large array x 157 ops/sec ±0.73% (86 runs sampled)
fast-json-stringify large array default x 48.72 ops/sec ±4.92% (48 runs sampled)
fast-json-stringify large array json-stringify x 157 ops/sec ±0.76% (86 runs sampled)
compile-json-stringify large array x 175 ops/sec ±4.47% (79 runs sampled)
AJV Serialize large array x 58.76 ops/sec ±4.59% (60 runs sampled)
Security notice
Treat the schema definition as application code, it
is not safe to use user-provided schemas.
To achieve low cost and high performance redaction fast-json-stringify
creates and compiles a function (using the Function constructor) on initialization.
While the schema is currently validated for any developer errors,
there is no guarantee that supplying user-generated schema could not
expose your application to remote attacks.
Users are responsible for sending trusted data. fast-json-stringify guarantees that you will get
a valid output only if your input matches the schema or can be coerced to the schema. If your input
doesn't match the schema, you will get undefined behavior.
Debug Mode
The debug mode can be activated during your development to understand what is going on when things do not
work as you expect.
const debugCompiled = fastJson({
title: 'default string',
type: 'object',
properties: {
firstName: {
type: 'string'
}
}
}, { mode: 'debug' })
console.log(debugCompiled) // it is a object contain code, ajv instance
const rawString = debugCompiled.code // it is the generated code
console.log(rawString)
const stringify = fastJson.restore(debugCompiled) // use the generated string to get back the `stringify` function
console.log(stringify({ firstName: 'Foo', surname: 'bar' })) // '{"firstName":"Foo"}'
Standalone Mode
The standalone mode is used to compile the code that can be directly run by node
itself. You need to have fast-json-stringify installed for the standalone code to work.
const fs = require('fs')
const code = fastJson({
title: 'default string',
type: 'object',
properties: {
firstName: {
type: 'string'
}
}
}, { mode: 'standalone', inlineValidators: true })
fs.writeFileSync('stringify.js', code)
const stringify = require('stringify.js')
console.log(stringify({ firstName: 'Foo', surname: 'bar' })) // '{"firstName":"Foo"}'
Set inlineValidators to true to include the Ajv-generated functions used by
anyOf, oneOf, and if/then/else in the same output file. This avoids
rebuilding an Ajv instance and compiling their schemas at runtime. The
generated module still requires fast-json-stringify for its serializer and
may require Ajv runtime helpers used by the generated validation code. Custom
Ajv formats used by these schemas must support Ajv's standalone code
generation.
The generated code also bakes in the length cutoffs used for
number and integer arrays. Those cutoffs are chosen for the
Node.js version that ran build(); a module built on Node.js 20 and run on
Node.js 22 or later keeps the Node.js 20 cutoffs, and vice versa. The output is
the same either way, only the speed differs.
Acknowledgments
This project was kindly sponsored by nearForm.
License
Licensed under MIT.
: {
type: 'string'
}
}
})
const obj = {
nickname: 'nick',
matchfoo: 42,
otherfoo: 'str',
matchnum: 3
}
console.log(stringify(obj)) // '{"matchfoo":"42","otherfoo":"str","matchnum":3,"nickname":"nick"}'
Additional properties
__INLINE_CODE_84__ supports additional properties as defined by JSON schema.
additionalProperties must be an object or a boolean, declared in this way: __INLINE_CODE_85__.
additionalProperties will work only for the properties that are not explicitly listed in the properties and patternProperties objects.
If additionalProperties is not present or is set to __INLINE_CODE_86__, every property that is not explicitly listed in the properties and patternProperties objects will be ignored, as described in Missing fields.
Missing fields are ignored to avoid having to rewrite objects before serializing. However, other schema rules would throw in similar situations.
If additionalProperties is set to __INLINE_CODE_87__, it will be used by __INLINE_CODE_88__ to stringify the additional properties. If you want to achieve maximum performance, we strongly encourage you to use a fixed schema where possible.
The additional properties will always be serialized at the end of the object.
Example:
__CODE_BLOCK_8__
AnyOf and OneOf
__INLINE_CODE_89__ supports the anyOf and oneOf keywords as defined by JSON schema. Both must be an array of valid JSON schemas. The different schemas will be tested in the specified order. The more schemas __INLINE_CODE_90__ has to try before finding a match, the slower it will be.
anyOf and oneOf use ajv as a JSON schema validator to find the schema that matches the data. This has an impact on performance—only use it as a last resort.
Example:
__CODE_BLOCK_9__
When specifying object JSON schemas for anyOf, add required validation keyword to match only the objects with the properties you want.
Example:
__CODE_BLOCK_10__
If/then/else
__INLINE_CODE_91__ supports __INLINE_CODE_92__ jsonschema feature. See ajv documentation.
Example:
__CODE_BLOCK_11__
NB Do not declare the properties twice or you will print them twice!
Reuse - $ref
If you want to reuse a definition of a value, you can use the property __INLINE_CODE_93__.
The value of __INLINE_CODE_94__ must be a string in JSON Pointer format.
Example:
__CODE_BLOCK_12__
If you need to use an external definition, you can pass it as an option to __INLINE_CODE_95__.
Example:
__CODE_BLOCK_13__
External definitions can also reference each other.
Example:
__CODE_BLOCK_14__
Long integers
By default, the library will handle automatically BigInt.
Integers
The __INLINE_CODE_96__ property will be truncated if a floating point is provided.
You can customize this behavior with the __INLINE_CODE_97__ option that will accept __INLINE_CODE_98__, __INLINE_CODE_99__, __INLINE_CODE_100__, or __INLINE_CODE_101__. Default is __INLINE_CODE_102__:
__CODE_BLOCK_15__
Nullable
According to the Open API 3.0 specification, a value that can be null must be declared __INLINE_CODE_103__.
Nullable object
__CODE_BLOCK_16__
Otherwise, instead of raising an error, null values will be coerced as follows:
- __INLINE_CODE_104__ -> __INLINE_CODE_105__
- __INLINE_CODE_106__ -> __INLINE_CODE_107__
- __INLINE_CODE_108__ -> __INLINE_CODE_109__
- __INLINE_CODE_110__ -> __INLINE_CODE_111__
- __INLINE_CODE_112__ -> __INLINE_CODE_113__
- __INLINE_CODE_114__ -> __INLINE_CODE_115__
Large Arrays
Large arrays are, for the scope of this document, defined as arrays containing,
by default, __INLINE_CODE_116__ elements or more. That value can be adjusted via the option
parameter __INLINE_CODE_117__.
At some point the overhead caused by the default mechanism used by
__INLINE_CODE_118__ to handle arrays starts increasing exponentially, leading
to slow overall executions.
Settings
In order to improve that the user can set the __INLINE_CODE_119__ and
__INLINE_CODE_120__ options.
__INLINE_CODE_121__'s default value is __INLINE_CODE_122__. Valid values for it are:
- __INLINE_CODE_123__ - This option keeps the full feature set of this lib: every element
is still coerced to the type declared in __INLINE_CODE_124__, and type errors are still
thrown. Arrays of plain primitives (__INLINE_CODE_125__, __INLINE_CODE_126__, __INLINE_CODE_127__) use a
copy-then-__INLINE_CODE_128__ strategy with their own length cutoffs, see
Primitive arrays below; __INLINE_CODE_129__ does not affect
them.
- __INLINE_CODE_130__ - This option will remove support for schema validation
within large arrays completely. By doing so the overhead previously
mentioned is nulled, greatly improving execution time. Mind there's no change
in behavior for arrays not considered large
__INLINE_CODE_131__'s default value is __INLINE_CODE_132__. Valid values for it are
integer-like values, such as:
- __INLINE_CODE_133__
- __INLINE_CODE_134__
- __INLINE_CODE_135__
- __INLINE_CODE_136__ - note this will be converted to __INLINE_CODE_137__, not __INLINE_CODE_138__
- __INLINE_CODE_139__ - note this will be converted to __INLINE_CODE_140__
Primitive arrays
With __INLINE_CODE_141__, arrays whose __INLINE_CODE_142__ schema is a plain
__INLINE_CODE_143__, __INLINE_CODE_144__ or __INLINE_CODE_145__ (no
__INLINE_CODE_146__, __INLINE_CODE_147__, __INLINE_CODE_148__, or combinator keyword; a __INLINE_CODE_149__ to a plain
string schema also qualifies, a __INLINE_CODE_150__ for __INLINE_CODE_151__/__INLINE_CODE_152__ does not) are
serialized by copying the elements into a fresh array and calling the native
__INLINE_CODE_153__ on the copy, which is several times faster than appending one
element at a time. This happens at or above a length cutoff that does not
depend on __INLINE_CODE_154__. The cutoffs are the array lengths above which the
copy beats the per-element loop, and they depend on the V8 version:
Node.js
__INLINE_CODE_155__
__INLINE_CODE_156__
__INLINE_CODE_157__
22 and later
8 elements
128 elements
512 elements
20
128 elements
300000 elements
300000 elements
Below the cutoff the array is serialized element by element as before. Above
it, the elements are copied until one is found that is not already of the
declared type (not a string, not a __INLINE_CODE_158__, or not an integer for __INLINE_CODE_159__);
that element and the ones after it go through the regular per-element coercion
and validation (__INLINE_CODE_160__ still becomes __INLINE_CODE_161__ or __INLINE_CODE_162__, __INLINE_CODE_163__ still applies,
__INLINE_CODE_164__ still throws). The output is byte-identical either way. Arrays of
__INLINE_CODE_165__, __INLINE_CODE_166__, objects, or any other __INLINE_CODE_167__ schema are not affected.
In standalone mode the cutoffs are baked into the generated
code, so the generated module inherits the cutoffs of the Node.js version that
ran __INLINE_CODE_168__, not of the one that runs the module.
Object mechanism
By default every object is serialized by generated code that appends the JSON
text property by property. Since V8 13.8 (Node.js 25+) the native
__INLINE_CODE_169__ has a side-effect-free fast path that no JavaScript string
building can match, but that fast path bails out as soon as the input carries a
__INLINE_CODE_170__, a __INLINE_CODE_171__, a class prototype or any property that is not plain data.
With __INLINE_CODE_172__, __INLINE_CODE_173__ instead projects the
input into preallocated scratch objects whose keys are exactly the schema
properties, in schema order, and hands the scratch to the native
__INLINE_CODE_174__. The scratch is plain by construction, so the fast path is
always taken, whatever the input looks like, while the schema is still applied:
undeclared properties are dropped, __INLINE_CODE_175__ is enforced, __INLINE_CODE_176__ applied,
types coerced and __INLINE_CODE_177__ / __INLINE_CODE_178__ / __INLINE_CODE_179__ rendered as before.
The output is byte-identical to the default mechanism.
__CODE_BLOCK_17__
The projection covers schemas made only of:
- __INLINE_CODE_180__ with __INLINE_CODE_181__ (no __INLINE_CODE_182__, __INLINE_CODE_183__,
__INLINE_CODE_184__, __INLINE_CODE_185__, combinators or __INLINE_CODE_186__), with at least one property;
- __INLINE_CODE_187__ (optionally with __INLINE_CODE_188__, __INLINE_CODE_189__ or __INLINE_CODE_190__), __INLINE_CODE_191__,
__INLINE_CODE_192__, __INLINE_CODE_193__, without __INLINE_CODE_194__, __INLINE_CODE_195__, __INLINE_CODE_196__, combinators or __INLINE_CODE_197__;
- the two-element forms __INLINE_CODE_198__, __INLINE_CODE_199__ and
__INLINE_CODE_200__ (in either order), with the same strict type dispatch
as the default mechanism;
- __INLINE_CODE_201__ whose __INLINE_CODE_202__ is one such schema (no tuples), nested to any depth;
every array element gets its own scratch objects.
Any other schema silently uses the default mechanism, so the option is always
safe to enable. The projection only pays off where V8 has the fast path, so on
Node.js 24 and older the option is a no-op and the default mechanism is used.
On Node.js 25+ the projection serializes plain objects within 5-15% of a bare
__INLINE_CODE_203__ (the default mechanism is 35-50% behind it there), and objects
coming from an ORM (extra columns, __INLINE_CODE_204__ instances, class prototypes) several
times faster than both the default mechanism and __INLINE_CODE_205__ itself.
The scratch objects are reused across calls; __INLINE_CODE_206__ is synchronous,
so this is safe, and no reference to the last input is retained beyond the
primitive values written into the scratch.
Unsafe string
By default, the library escapes all strings. With the 'unsafe' format, the string isn't escaped. This has a potentially dangerous security issue. You can use it only if you are sure that your data doesn't need escaping. The advantage is a significant performance improvement.
Example:
__CODE_BLOCK_18__
Benchmarks
For reference, here are some benchmarks for comparison over the three
mechanisms. Benchmarks were conducted on an old machine.
- Machine: __INLINE_CODE_207__.
- Node.js __INLINE_CODE_208__
__CODE_BLOCK_19__
Security notice
Treat the schema definition as application code, it
is not safe to use user-provided schemas.
To achieve low cost and high performance redaction __INLINE_CODE_209__
creates and compiles a function (using the __INLINE_CODE_210__ constructor) on initialization.
While the __INLINE_CODE_211__ is currently validated for any developer errors,
there is no guarantee that supplying user-generated schema could not
expose your application to remote attacks.
Users are responsible for sending trusted data. __INLINE_CODE_212__ guarantees that you will get
a valid output only if your input matches the schema or can be coerced to the schema. If your input
doesn't match the schema, you will get undefined behavior.
Debug Mode
The debug mode can be activated during your development to understand what is going on when things do not
work as you expect.
__CODE_BLOCK_20__
Standalone Mode
The standalone mode is used to compile the code that can be directly run by __INLINE_CODE_213__
itself. You need to have __INLINE_CODE_214__ installed for the standalone code to work.
__CODE_BLOCK_21__
Set __INLINE_CODE_215__ to __INLINE_CODE_216__ to include the Ajv-generated functions used by
__INLINE_CODE_217__, __INLINE_CODE_218__, and __INLINE_CODE_219__ in the same output file. This avoids
rebuilding an Ajv instance and compiling their schemas at runtime. The
generated module still requires __INLINE_CODE_220__ for its serializer and
may require Ajv runtime helpers used by the generated validation code. Custom
Ajv formats used by these schemas must support Ajv's standalone code
generation.
The generated code also bakes in the length cutoffs used for
number and integer arrays. Those cutoffs are chosen for the
Node.js version that ran __INLINE_CODE_221__; a module built on Node.js 20 and run on
Node.js 22 or later keeps the Node.js 20 cutoffs, and vice versa. The output is
the same either way, only the speed differs.
Acknowledgments
This project was kindly sponsored by nearForm.
License
Licensed under MIT.
: {
type: 'string'
}
},
additionalProperties: {
type: 'string'
}
})
const obj = {
nickname: 'nick',
matchfoo: 42,
otherfoo: 'str',
matchnum: 3,
nomatchstr: 'valar morghulis',
nomatchint: 313
}
console.log(stringify(obj)) // '{"nickname":"nick","matchfoo":"42","otherfoo":"str","matchnum":3,"nomatchstr":"valar morghulis",nomatchint:"313"}'
AnyOf and OneOf
__INLINE_CODE_89__ supports the anyOf and oneOf keywords as defined by JSON schema. Both must be an array of valid JSON schemas. The different schemas will be tested in the specified order. The more schemas __INLINE_CODE_90__ has to try before finding a match, the slower it will be.
anyOf and oneOf use ajv as a JSON schema validator to find the schema that matches the data. This has an impact on performance—only use it as a last resort.
Example:
__CODE_BLOCK_9__When specifying object JSON schemas for anyOf, add required validation keyword to match only the objects with the properties you want.
Example:
__CODE_BLOCK_10__If/then/else
__INLINE_CODE_91__ supports __INLINE_CODE_92__ jsonschema feature. See ajv documentation.
Example:
__CODE_BLOCK_11__NB Do not declare the properties twice or you will print them twice!
Reuse - $ref
If you want to reuse a definition of a value, you can use the property __INLINE_CODE_93__. The value of __INLINE_CODE_94__ must be a string in JSON Pointer format. Example:
__CODE_BLOCK_12__If you need to use an external definition, you can pass it as an option to __INLINE_CODE_95__. Example:
__CODE_BLOCK_13__External definitions can also reference each other. Example:
__CODE_BLOCK_14__Long integers
By default, the library will handle automatically BigInt.
Integers
The __INLINE_CODE_96__ property will be truncated if a floating point is provided. You can customize this behavior with the __INLINE_CODE_97__ option that will accept __INLINE_CODE_98__, __INLINE_CODE_99__, __INLINE_CODE_100__, or __INLINE_CODE_101__. Default is __INLINE_CODE_102__:
__CODE_BLOCK_15__Nullable
According to the Open API 3.0 specification, a value that can be null must be declared __INLINE_CODE_103__.
Nullable object
__CODE_BLOCK_16__Otherwise, instead of raising an error, null values will be coerced as follows:
- __INLINE_CODE_104__ -> __INLINE_CODE_105__
- __INLINE_CODE_106__ -> __INLINE_CODE_107__
- __INLINE_CODE_108__ -> __INLINE_CODE_109__
- __INLINE_CODE_110__ -> __INLINE_CODE_111__
- __INLINE_CODE_112__ -> __INLINE_CODE_113__
- __INLINE_CODE_114__ -> __INLINE_CODE_115__
Large Arrays
Large arrays are, for the scope of this document, defined as arrays containing, by default, __INLINE_CODE_116__ elements or more. That value can be adjusted via the option parameter __INLINE_CODE_117__.
At some point the overhead caused by the default mechanism used by __INLINE_CODE_118__ to handle arrays starts increasing exponentially, leading to slow overall executions.
Settings
In order to improve that the user can set the __INLINE_CODE_119__ and __INLINE_CODE_120__ options.
__INLINE_CODE_121__'s default value is __INLINE_CODE_122__. Valid values for it are:
- __INLINE_CODE_123__ - This option keeps the full feature set of this lib: every element is still coerced to the type declared in __INLINE_CODE_124__, and type errors are still thrown. Arrays of plain primitives (__INLINE_CODE_125__, __INLINE_CODE_126__, __INLINE_CODE_127__) use a copy-then-__INLINE_CODE_128__ strategy with their own length cutoffs, see Primitive arrays below; __INLINE_CODE_129__ does not affect them.
- __INLINE_CODE_130__ - This option will remove support for schema validation within large arrays completely. By doing so the overhead previously mentioned is nulled, greatly improving execution time. Mind there's no change in behavior for arrays not considered large
__INLINE_CODE_131__'s default value is __INLINE_CODE_132__. Valid values for it are integer-like values, such as:
- __INLINE_CODE_133__
- __INLINE_CODE_134__
- __INLINE_CODE_135__
- __INLINE_CODE_136__ - note this will be converted to __INLINE_CODE_137__, not __INLINE_CODE_138__
- __INLINE_CODE_139__ - note this will be converted to __INLINE_CODE_140__
Primitive arrays
With __INLINE_CODE_141__, arrays whose __INLINE_CODE_142__ schema is a plain __INLINE_CODE_143__, __INLINE_CODE_144__ or __INLINE_CODE_145__ (no __INLINE_CODE_146__, __INLINE_CODE_147__, __INLINE_CODE_148__, or combinator keyword; a __INLINE_CODE_149__ to a plain string schema also qualifies, a __INLINE_CODE_150__ for __INLINE_CODE_151__/__INLINE_CODE_152__ does not) are serialized by copying the elements into a fresh array and calling the native __INLINE_CODE_153__ on the copy, which is several times faster than appending one element at a time. This happens at or above a length cutoff that does not depend on __INLINE_CODE_154__. The cutoffs are the array lengths above which the copy beats the per-element loop, and they depend on the V8 version:
| Node.js | __INLINE_CODE_155__ | __INLINE_CODE_156__ | __INLINE_CODE_157__ |
|---|---|---|---|
| 22 and later | 8 elements | 128 elements | 512 elements |
| 20 | 128 elements | 300000 elements | 300000 elements |
Below the cutoff the array is serialized element by element as before. Above it, the elements are copied until one is found that is not already of the declared type (not a string, not a __INLINE_CODE_158__, or not an integer for __INLINE_CODE_159__); that element and the ones after it go through the regular per-element coercion and validation (__INLINE_CODE_160__ still becomes __INLINE_CODE_161__ or __INLINE_CODE_162__, __INLINE_CODE_163__ still applies, __INLINE_CODE_164__ still throws). The output is byte-identical either way. Arrays of __INLINE_CODE_165__, __INLINE_CODE_166__, objects, or any other __INLINE_CODE_167__ schema are not affected.
In standalone mode the cutoffs are baked into the generated code, so the generated module inherits the cutoffs of the Node.js version that ran __INLINE_CODE_168__, not of the one that runs the module.
Object mechanism
By default every object is serialized by generated code that appends the JSON text property by property. Since V8 13.8 (Node.js 25+) the native __INLINE_CODE_169__ has a side-effect-free fast path that no JavaScript string building can match, but that fast path bails out as soon as the input carries a __INLINE_CODE_170__, a __INLINE_CODE_171__, a class prototype or any property that is not plain data.
With __INLINE_CODE_172__, __INLINE_CODE_173__ instead projects the input into preallocated scratch objects whose keys are exactly the schema properties, in schema order, and hands the scratch to the native __INLINE_CODE_174__. The scratch is plain by construction, so the fast path is always taken, whatever the input looks like, while the schema is still applied: undeclared properties are dropped, __INLINE_CODE_175__ is enforced, __INLINE_CODE_176__ applied, types coerced and __INLINE_CODE_177__ / __INLINE_CODE_178__ / __INLINE_CODE_179__ rendered as before. The output is byte-identical to the default mechanism.
__CODE_BLOCK_17__The projection covers schemas made only of:
- __INLINE_CODE_180__ with __INLINE_CODE_181__ (no __INLINE_CODE_182__, __INLINE_CODE_183__, __INLINE_CODE_184__, __INLINE_CODE_185__, combinators or __INLINE_CODE_186__), with at least one property;
- __INLINE_CODE_187__ (optionally with __INLINE_CODE_188__, __INLINE_CODE_189__ or __INLINE_CODE_190__), __INLINE_CODE_191__, __INLINE_CODE_192__, __INLINE_CODE_193__, without __INLINE_CODE_194__, __INLINE_CODE_195__, __INLINE_CODE_196__, combinators or __INLINE_CODE_197__;
- the two-element forms __INLINE_CODE_198__, __INLINE_CODE_199__ and __INLINE_CODE_200__ (in either order), with the same strict type dispatch as the default mechanism;
- __INLINE_CODE_201__ whose __INLINE_CODE_202__ is one such schema (no tuples), nested to any depth; every array element gets its own scratch objects.
Any other schema silently uses the default mechanism, so the option is always safe to enable. The projection only pays off where V8 has the fast path, so on Node.js 24 and older the option is a no-op and the default mechanism is used. On Node.js 25+ the projection serializes plain objects within 5-15% of a bare __INLINE_CODE_203__ (the default mechanism is 35-50% behind it there), and objects coming from an ORM (extra columns, __INLINE_CODE_204__ instances, class prototypes) several times faster than both the default mechanism and __INLINE_CODE_205__ itself.
The scratch objects are reused across calls; __INLINE_CODE_206__ is synchronous, so this is safe, and no reference to the last input is retained beyond the primitive values written into the scratch.
Unsafe string
By default, the library escapes all strings. With the 'unsafe' format, the string isn't escaped. This has a potentially dangerous security issue. You can use it only if you are sure that your data doesn't need escaping. The advantage is a significant performance improvement.
Example:
__CODE_BLOCK_18__Benchmarks
For reference, here are some benchmarks for comparison over the three mechanisms. Benchmarks were conducted on an old machine.
- Machine: __INLINE_CODE_207__.
- Node.js __INLINE_CODE_208__
Security notice
Treat the schema definition as application code, it is not safe to use user-provided schemas.
To achieve low cost and high performance redaction __INLINE_CODE_209__ creates and compiles a function (using the __INLINE_CODE_210__ constructor) on initialization. While the __INLINE_CODE_211__ is currently validated for any developer errors, there is no guarantee that supplying user-generated schema could not expose your application to remote attacks.
Users are responsible for sending trusted data. __INLINE_CODE_212__ guarantees that you will get a valid output only if your input matches the schema or can be coerced to the schema. If your input doesn't match the schema, you will get undefined behavior.
Debug Mode
The debug mode can be activated during your development to understand what is going on when things do not work as you expect.
__CODE_BLOCK_20__Standalone Mode
The standalone mode is used to compile the code that can be directly run by __INLINE_CODE_213__ itself. You need to have __INLINE_CODE_214__ installed for the standalone code to work.
__CODE_BLOCK_21__Set __INLINE_CODE_215__ to __INLINE_CODE_216__ to include the Ajv-generated functions used by __INLINE_CODE_217__, __INLINE_CODE_218__, and __INLINE_CODE_219__ in the same output file. This avoids rebuilding an Ajv instance and compiling their schemas at runtime. The generated module still requires __INLINE_CODE_220__ for its serializer and may require Ajv runtime helpers used by the generated validation code. Custom Ajv formats used by these schemas must support Ajv's standalone code generation.
The generated code also bakes in the length cutoffs used for number and integer arrays. Those cutoffs are chosen for the Node.js version that ran __INLINE_CODE_221__; a module built on Node.js 20 and run on Node.js 22 or later keeps the Node.js 20 cutoffs, and vice versa. The output is the same either way, only the speed differs.
Acknowledgments
This project was kindly sponsored by nearForm.
License
Licensed under MIT.
: { type: 'string' } } }) const obj = { nickname: 'nick', matchfoo: 42, otherfoo: 'str', matchnum: 3 } console.log(stringify(obj)) // '{"matchfoo":"42","otherfoo":"str","matchnum":3,"nickname":"nick"}'Additional properties
__INLINE_CODE_84__ supports additional properties as defined by JSON schema. additionalProperties must be an object or a boolean, declared in this way: __INLINE_CODE_85__. additionalProperties will work only for the properties that are not explicitly listed in the properties and patternProperties objects.
If additionalProperties is not present or is set to __INLINE_CODE_86__, every property that is not explicitly listed in the properties and patternProperties objects will be ignored, as described in Missing fields. Missing fields are ignored to avoid having to rewrite objects before serializing. However, other schema rules would throw in similar situations. If additionalProperties is set to __INLINE_CODE_87__, it will be used by __INLINE_CODE_88__ to stringify the additional properties. If you want to achieve maximum performance, we strongly encourage you to use a fixed schema where possible. The additional properties will always be serialized at the end of the object. Example:
__CODE_BLOCK_8__AnyOf and OneOf
__INLINE_CODE_89__ supports the anyOf and oneOf keywords as defined by JSON schema. Both must be an array of valid JSON schemas. The different schemas will be tested in the specified order. The more schemas __INLINE_CODE_90__ has to try before finding a match, the slower it will be.
anyOf and oneOf use ajv as a JSON schema validator to find the schema that matches the data. This has an impact on performance—only use it as a last resort.
Example:
__CODE_BLOCK_9__When specifying object JSON schemas for anyOf, add required validation keyword to match only the objects with the properties you want.
Example:
__CODE_BLOCK_10__If/then/else
__INLINE_CODE_91__ supports __INLINE_CODE_92__ jsonschema feature. See ajv documentation.
Example:
__CODE_BLOCK_11__NB Do not declare the properties twice or you will print them twice!
Reuse - $ref
If you want to reuse a definition of a value, you can use the property __INLINE_CODE_93__. The value of __INLINE_CODE_94__ must be a string in JSON Pointer format. Example:
__CODE_BLOCK_12__If you need to use an external definition, you can pass it as an option to __INLINE_CODE_95__. Example:
__CODE_BLOCK_13__External definitions can also reference each other. Example:
__CODE_BLOCK_14__Long integers
By default, the library will handle automatically BigInt.
Integers
The __INLINE_CODE_96__ property will be truncated if a floating point is provided. You can customize this behavior with the __INLINE_CODE_97__ option that will accept __INLINE_CODE_98__, __INLINE_CODE_99__, __INLINE_CODE_100__, or __INLINE_CODE_101__. Default is __INLINE_CODE_102__:
__CODE_BLOCK_15__Nullable
According to the Open API 3.0 specification, a value that can be null must be declared __INLINE_CODE_103__.
Nullable object
__CODE_BLOCK_16__Otherwise, instead of raising an error, null values will be coerced as follows:
- __INLINE_CODE_104__ -> __INLINE_CODE_105__
- __INLINE_CODE_106__ -> __INLINE_CODE_107__
- __INLINE_CODE_108__ -> __INLINE_CODE_109__
- __INLINE_CODE_110__ -> __INLINE_CODE_111__
- __INLINE_CODE_112__ -> __INLINE_CODE_113__
- __INLINE_CODE_114__ -> __INLINE_CODE_115__
Large Arrays
Large arrays are, for the scope of this document, defined as arrays containing, by default, __INLINE_CODE_116__ elements or more. That value can be adjusted via the option parameter __INLINE_CODE_117__.
At some point the overhead caused by the default mechanism used by __INLINE_CODE_118__ to handle arrays starts increasing exponentially, leading to slow overall executions.
Settings
In order to improve that the user can set the __INLINE_CODE_119__ and __INLINE_CODE_120__ options.
__INLINE_CODE_121__'s default value is __INLINE_CODE_122__. Valid values for it are:
- __INLINE_CODE_123__ - This option keeps the full feature set of this lib: every element is still coerced to the type declared in __INLINE_CODE_124__, and type errors are still thrown. Arrays of plain primitives (__INLINE_CODE_125__, __INLINE_CODE_126__, __INLINE_CODE_127__) use a copy-then-__INLINE_CODE_128__ strategy with their own length cutoffs, see Primitive arrays below; __INLINE_CODE_129__ does not affect them.
- __INLINE_CODE_130__ - This option will remove support for schema validation within large arrays completely. By doing so the overhead previously mentioned is nulled, greatly improving execution time. Mind there's no change in behavior for arrays not considered large
__INLINE_CODE_131__'s default value is __INLINE_CODE_132__. Valid values for it are integer-like values, such as:
- __INLINE_CODE_133__
- __INLINE_CODE_134__
- __INLINE_CODE_135__
- __INLINE_CODE_136__ - note this will be converted to __INLINE_CODE_137__, not __INLINE_CODE_138__
- __INLINE_CODE_139__ - note this will be converted to __INLINE_CODE_140__
Primitive arrays
With __INLINE_CODE_141__, arrays whose __INLINE_CODE_142__ schema is a plain __INLINE_CODE_143__, __INLINE_CODE_144__ or __INLINE_CODE_145__ (no __INLINE_CODE_146__, __INLINE_CODE_147__, __INLINE_CODE_148__, or combinator keyword; a __INLINE_CODE_149__ to a plain string schema also qualifies, a __INLINE_CODE_150__ for __INLINE_CODE_151__/__INLINE_CODE_152__ does not) are serialized by copying the elements into a fresh array and calling the native __INLINE_CODE_153__ on the copy, which is several times faster than appending one element at a time. This happens at or above a length cutoff that does not depend on __INLINE_CODE_154__. The cutoffs are the array lengths above which the copy beats the per-element loop, and they depend on the V8 version:
| Node.js | __INLINE_CODE_155__ | __INLINE_CODE_156__ | __INLINE_CODE_157__ |
|---|---|---|---|
| 22 and later | 8 elements | 128 elements | 512 elements |
| 20 | 128 elements | 300000 elements | 300000 elements |
Below the cutoff the array is serialized element by element as before. Above it, the elements are copied until one is found that is not already of the declared type (not a string, not a __INLINE_CODE_158__, or not an integer for __INLINE_CODE_159__); that element and the ones after it go through the regular per-element coercion and validation (__INLINE_CODE_160__ still becomes __INLINE_CODE_161__ or __INLINE_CODE_162__, __INLINE_CODE_163__ still applies, __INLINE_CODE_164__ still throws). The output is byte-identical either way. Arrays of __INLINE_CODE_165__, __INLINE_CODE_166__, objects, or any other __INLINE_CODE_167__ schema are not affected.
In standalone mode the cutoffs are baked into the generated code, so the generated module inherits the cutoffs of the Node.js version that ran __INLINE_CODE_168__, not of the one that runs the module.
Object mechanism
By default every object is serialized by generated code that appends the JSON text property by property. Since V8 13.8 (Node.js 25+) the native __INLINE_CODE_169__ has a side-effect-free fast path that no JavaScript string building can match, but that fast path bails out as soon as the input carries a __INLINE_CODE_170__, a __INLINE_CODE_171__, a class prototype or any property that is not plain data.
With __INLINE_CODE_172__, __INLINE_CODE_173__ instead projects the input into preallocated scratch objects whose keys are exactly the schema properties, in schema order, and hands the scratch to the native __INLINE_CODE_174__. The scratch is plain by construction, so the fast path is always taken, whatever the input looks like, while the schema is still applied: undeclared properties are dropped, __INLINE_CODE_175__ is enforced, __INLINE_CODE_176__ applied, types coerced and __INLINE_CODE_177__ / __INLINE_CODE_178__ / __INLINE_CODE_179__ rendered as before. The output is byte-identical to the default mechanism.
__CODE_BLOCK_17__The projection covers schemas made only of:
- __INLINE_CODE_180__ with __INLINE_CODE_181__ (no __INLINE_CODE_182__, __INLINE_CODE_183__, __INLINE_CODE_184__, __INLINE_CODE_185__, combinators or __INLINE_CODE_186__), with at least one property;
- __INLINE_CODE_187__ (optionally with __INLINE_CODE_188__, __INLINE_CODE_189__ or __INLINE_CODE_190__), __INLINE_CODE_191__, __INLINE_CODE_192__, __INLINE_CODE_193__, without __INLINE_CODE_194__, __INLINE_CODE_195__, __INLINE_CODE_196__, combinators or __INLINE_CODE_197__;
- the two-element forms __INLINE_CODE_198__, __INLINE_CODE_199__ and __INLINE_CODE_200__ (in either order), with the same strict type dispatch as the default mechanism;
- __INLINE_CODE_201__ whose __INLINE_CODE_202__ is one such schema (no tuples), nested to any depth; every array element gets its own scratch objects.
Any other schema silently uses the default mechanism, so the option is always safe to enable. The projection only pays off where V8 has the fast path, so on Node.js 24 and older the option is a no-op and the default mechanism is used. On Node.js 25+ the projection serializes plain objects within 5-15% of a bare __INLINE_CODE_203__ (the default mechanism is 35-50% behind it there), and objects coming from an ORM (extra columns, __INLINE_CODE_204__ instances, class prototypes) several times faster than both the default mechanism and __INLINE_CODE_205__ itself.
The scratch objects are reused across calls; __INLINE_CODE_206__ is synchronous, so this is safe, and no reference to the last input is retained beyond the primitive values written into the scratch.
Unsafe string
By default, the library escapes all strings. With the 'unsafe' format, the string isn't escaped. This has a potentially dangerous security issue. You can use it only if you are sure that your data doesn't need escaping. The advantage is a significant performance improvement.
Example:
__CODE_BLOCK_18__Benchmarks
For reference, here are some benchmarks for comparison over the three mechanisms. Benchmarks were conducted on an old machine.
- Machine: __INLINE_CODE_207__.
- Node.js __INLINE_CODE_208__
Security notice
Treat the schema definition as application code, it is not safe to use user-provided schemas.
To achieve low cost and high performance redaction __INLINE_CODE_209__ creates and compiles a function (using the __INLINE_CODE_210__ constructor) on initialization. While the __INLINE_CODE_211__ is currently validated for any developer errors, there is no guarantee that supplying user-generated schema could not expose your application to remote attacks.
Users are responsible for sending trusted data. __INLINE_CODE_212__ guarantees that you will get a valid output only if your input matches the schema or can be coerced to the schema. If your input doesn't match the schema, you will get undefined behavior.
Debug Mode
The debug mode can be activated during your development to understand what is going on when things do not work as you expect.
__CODE_BLOCK_20__Standalone Mode
The standalone mode is used to compile the code that can be directly run by __INLINE_CODE_213__ itself. You need to have __INLINE_CODE_214__ installed for the standalone code to work.
__CODE_BLOCK_21__Set __INLINE_CODE_215__ to __INLINE_CODE_216__ to include the Ajv-generated functions used by __INLINE_CODE_217__, __INLINE_CODE_218__, and __INLINE_CODE_219__ in the same output file. This avoids rebuilding an Ajv instance and compiling their schemas at runtime. The generated module still requires __INLINE_CODE_220__ for its serializer and may require Ajv runtime helpers used by the generated validation code. Custom Ajv formats used by these schemas must support Ajv's standalone code generation.
The generated code also bakes in the length cutoffs used for number and integer arrays. Those cutoffs are chosen for the Node.js version that ran __INLINE_CODE_221__; a module built on Node.js 20 and run on Node.js 22 or later keeps the Node.js 20 cutoffs, and vice versa. The output is the same either way, only the speed differs.
Acknowledgments
This project was kindly sponsored by nearForm.
License
Licensed under MIT.