Nominally Patriotic Meathead

    fast-json-stringify
    TypeScript icon, indicating that this package has built-in type declarations

    3.0.0 • Public • Published

    fast-json-stringify

    CI NPM version Known Vulnerabilities js-standard-style NPM downloads

    fast-json-stringify is significantly faster than JSON.stringify() for small payloads.
    Its performance advantage shrinks as your payload grows.
    It pairs well with flatstr, which triggers a V8 optimization that improves performance when eventually converting the string to a Buffer.

    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 v16.9.1
    FJS creation x 8,443 ops/sec ±1.01% (90 runs sampled)
    CJS creation x 183,219 ops/sec ±0.13% (96 runs sampled)
    AJV Serialize creation x 83,541,848 ops/sec ±0.24% (98 runs sampled)
    JSON.stringify array x 5,363 ops/sec ±0.11% (100 runs sampled)
    fast-json-stringify array x 6,747 ops/sec ±0.13% (98 runs sampled)
    compile-json-stringify array x 7,121 ops/sec ±0.42% (98 runs sampled)
    AJV Serialize array x 7,533 ops/sec ±0.13% (98 runs sampled)
    JSON.stringify long string x 16,461 ops/sec ±0.12% (98 runs sampled)
    fast-json-stringify long string x 16,443 ops/sec ±0.37% (99 runs sampled)
    compile-json-stringify long string x 16,458 ops/sec ±0.09% (98 runs sampled)
    AJV Serialize long string x 21,433 ops/sec ±0.08% (95 runs sampled)
    JSON.stringify short string x 12,035,664 ops/sec ±0.62% (96 runs sampled)
    fast-json-stringify short string x 38,281,060 ops/sec ±0.24% (98 runs sampled)
    compile-json-stringify short string x 32,388,037 ops/sec ±0.27% (97 runs sampled)
    AJV Serialize short string x 32,288,612 ops/sec ±0.32% (95 runs sampled)
    JSON.stringify obj x 3,068,185 ops/sec ±0.16% (98 runs sampled)
    fast-json-stringify obj x 10,082,694 ops/sec ±0.10% (97 runs sampled)
    compile-json-stringify obj x 17,037,963 ops/sec ±1.17% (97 runs sampled)
    AJV Serialize obj x 9,660,041 ops/sec ±0.11% (97 runs sampled)
    JSON stringify date x 1,084,008 ops/sec ±0.16% (98 runs sampled)
    fast-json-stringify date format x 1,781,044 ops/sec ±0.48% (99 runs sampled)
    compile-json-stringify date format x 1,086,187 ops/sec ±0.16% (99 runs sampled)
    

    Table of contents:

    Try it out on RunKit: https://runkit.com/npm/fast-json-stringify

    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 second parameter:

    const fastJson = require('fast-json-stringify')
    const stringify = fastJson(mySchema, {
      schema: { ... },
      ajv: { ... },
      rounding: 'ceil'
    })

    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 string formatted Date and not Date Object, there will be no manipulation on it. It should be properly formatted.

    Example with a MomentJS object:

    const moment = require('moment')
    
    const stringify = fastJson({
      title: 'Example Schema with string date-time field',
      type: 'string',
      format: 'date-time'
    })
    
    console.log(stringify(moment())) // '"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$': {
          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

    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 serialzied at the end of the object. Example:

    const stringify = fastJson({
      title: 'Example Schema',
      type: 'object',
      properties: {
        nickname: {
          type: 'string'
        }
      },
      patternProperties: {
        'num': {
          type: 'number'
        },
        '.*foo$': {
          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

    fast-json-stringify supports the anyOf keyword as defined by JSON schema. anyOf 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 uses 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 behaviour with the rounding option that will accept round, ceil or floor:

    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

    Security notice

    Treat the schema definition as application code, it is not safe to use user-provided schemas.

    In order to achieve lowest cost/highest 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.

    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'
        }
      }
    }, { debugMode: true })
    
    console.log(debugCompiled) // it is an array of functions that can create your `stringify` function
    console.log(debugCompiled.toString()) // print a "ready to read" string function, you can save it to a file
    
    const rawString = debugCompiled.toString()
    const stringify = fastJson.restore(rawString) // use the generated string to get back the `stringify` function
    console.log(stringify({ firstName: 'Foo', surname: 'bar' })) // '{"firstName":"Foo"}'

    Acknowledgements

    This project was kindly sponsored by nearForm.

    License

    MIT

    Install

    npm i fast-json-stringify

    DownloadsWeekly Downloads

    681,114

    Version

    3.0.0

    License

    MIT

    Unpacked Size

    269 kB

    Total Files

    65

    Last publish

    Collaborators

    • delvedor
    • matteo.collina