npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

Iโ€™ve always been into building performant and accessible sites, but lately Iโ€™ve been taking it extremely seriously. So much so that Iโ€™ve been building a tool to help me optimize and monitor the sites that I build to make sure that Iโ€™m making an attempt to offer the best experience to those who visit them. If youโ€™re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, ๐Ÿ‘‹, Iโ€™m Ryan Hefnerย  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If youโ€™re interested in other things Iโ€™m working on, follow me on Twitter or check out the open source projects Iโ€™ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soonโ€“ish.

Open Software & Tools

This site wouldnโ€™t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you ๐Ÿ™

ยฉ 2026 โ€“ย Pkg Stats / Ryan Hefner

@traversable/json-schema

v0.0.26

Published

<br> <h1 align="center">แฏ“๐˜๐—ฟ๐—ฎ๐˜ƒ๐—ฒ๐—ฟ๐˜€๐—ฎ๐—ฏ๐—น๐—ฒ/๐—ท๐˜€๐—ผ๐—ป-๐˜€๐—ฐ๐—ต๐—ฒ๐—บ๐—ฎ</h1> <br>

Readme

[!NOTE] Currently this package only supports JSON Schema Draft 2020-12

Getting started

$ pnpm add @traversable/json-schema

Here's an example of importing the library:

import { JsonSchema } from '@traversable/json-schema'

// or, if you prefer, you can use named imports:
import { deepClone, deepEqual } from '@traversable/json-schema'

// see below for specific examples

Table of contents

Fuzz-tested, production ready

Advanced

Features

JsonSchema.check

JsonSchema.check converts a JSON Schema into a super-performant type-guard.

Notes

  • Consistently better performance than Ajv
  • Works in any environment that supports defining functions using the Function constructor, including (as of May 2025) Cloudflare workers ๐ŸŽ‰

Performance comparison

Here's a Bolt sandbox if you'd like to run the benchmarks yourself.

        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
        โ”‚       Average  โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Ajv  โ”‚  1.57x faster  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Example

import { JsonSchema } from '@traversable/json-schema'

const check = JsonSchema.check({
  type: 'object',
  required: ['street1', 'city'],
  properties: {
    street1: { type: 'string' },
    street2: { type: 'string' },
    city: { type: 'string' },
  }
})

check({ street1: '221B Baker St', city: 'London' }) // => true
check({ street1: '221B Baker St' })                 // => false

See also

JsonSchema.check.writeable

JsonSchema.check converts a JSON Schema into a super-performant type-guard.

Compared to JsonSchema.check, JsonSchema.check.writeable returns the check function in stringified ("writeable") form.

Notes

  • Useful when you're consuming a set of JSON Schemas schemas and writing them all to disc
  • Also useful for testing purposes or for troubleshooting, since it gives you a way to "see" exactly what the check functions check

Example

import { JsonSchema } from '@traversable/json-schema'

const check = JsonSchema.check.writeable({
  type: 'object',
  required: ['firstName', 'address'],
  properties: {
    firstName: { type: 'string' },
    lastName: { type: 'string' },
    address: {
      type: 'object',
      required: ['street1', 'city', 'state'],
      properties: {
        street1: { type: 'string' },
        street2: { type: 'string' },
        city: { type: 'string' },
        state: { enum: ['AL', 'AK', 'AZ', '...'] }
      }
    }
  }
}, { typeName: 'User' })

console.log(check)
// Prints:
type User = {
  firstName: string
  lastName?: string
  address: {
    street1: string
    street2?: string
    city: string
    state: "AL" | "AK" | "AZ" | "..."
  }
}

function check(value: any): value is User {
  return (
    !!value &&
    typeof value === "object" &&
    typeof value.firstName === "string" &&
    (!Object.hasOwn(value, "lastName") || typeof value.lastName === "string") &&
    !!value.address &&
    typeof value.address === "object" &&
    typeof value.address.street1 === "string" &&
    (!Object.hasOwn(value.address, "street2") ||
      typeof value.address.street2 === "string") &&
    typeof value.address.city === "string" &&
    (value.address.state === "AL" ||
      value.address.state === "AK" ||
      value.address.state === "AZ" ||
      value.address.state === "...")
  )
}
import { JsonSchema } from '@traversable/json-schema'

const check = JsonSchema.check.writeable({
  $defs: {
    state: { enum: ['AL', 'AK', 'AZ', '...'] },
    address: {
      type: 'object',
      required: ['street1', 'city', 'state'],
      properties: {
        street1: { type: 'string' },
        street2: { type: 'string' },
        city: { type: 'string' },
        state: {
          $ref: '#/$defs/state'
        }
      }
    }
  },
  type: 'object',
  required: ['firstName', 'address'],
  properties: {
    firstName: { type: 'string' },
    lastName: { type: 'string' },
    address: {
      $ref: '#/$defs/address'
    }
  }
}, { typeName: 'User' })

console.log(check)
// Prints:
type State = "AL" | "AK" | "AZ" | "..."
type Address = {
  street1: string
  street2?: string
  city: string
  state: State 
}
type User = {
  firstName: string
  lastName?: string
  address: Address 
}

function checkState(value: any) {
  return value === "AL" || value === "AK" || value === "AZ" || value === "..."
}
function checkAddress(value: any) {
  return (
    !!value &&
    typeof value === "object" &&
    typeof value.street1 === "string" &&
    (!Object.hasOwn(value, "street2") || typeof value.street2 === "string") &&
    typeof value.city === "string" &&
    checkState(value.state)
  )
}
function check(value: any): value is User {
  return (
    !!value &&
    typeof value === "object" &&
    typeof value.firstName === "string" &&
    (!Object.hasOwn(value, "lastName") || typeof value.lastName === "string") &&
    checkAddress(value.address)
  )
}

See also

JsonSchema.deepClone

JsonSchema.deepClone lets users derive a specialized "deep copy" function that works with values that have been already validated.

Because the values have already been validated, clone times are significantly faster than alternatives like window.structuredClone and Lodash.cloneDeep.

Performance comparison

Here's a Bolt sandbox if you'd like to run the benchmarks yourself.

                           โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                           โ”‚        Average  โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Lodash.cloneDeep        โ”‚  13.99x faster  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  window.structuredClone  โ”‚  17.23x faster  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

This article goes into more detail about what makes JsonSchema.deepClone so fast.

Lodash                       868.72 ns/iter   1.00 ยตs                 โ–‡โ–ˆ
                      (269.22 ns โ€ฆ 1.20 ยตs)   1.14 ยตs                 โ–ˆโ–ˆ
                    (  8.05  b โ€ฆ 963.18  b) 307.93  b โ–ƒโ–‚โ–โ–โ–โ–โ–†โ–…โ–‡โ–ƒโ–‚โ–โ–โ–‚โ–ƒโ–ˆโ–ˆโ–ˆโ–…โ–ƒโ–
                  3.64 ipc (  1.47% stalls)  98.24% L1 data cache
          2.41k cycles   8.77k instructions  38.66% retired LD/ST (  3.39k)

structuredClone                1.07 ยตs/iter   1.08 ยตs    โ–„โ–ˆโ–„
                        (1.02 ยตs โ€ฆ 1.24 ยตs)   1.22 ยตs   โ–ƒโ–ˆโ–ˆโ–ˆโ–„โ–‚
                    ( 13.91  b โ€ฆ 369.62  b)  38.79  b โ–โ–…โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–„โ–‚โ–„โ–ƒโ–โ–‚โ–‚โ–‚โ–‚โ–‚โ–โ–โ–
                  4.35 ipc (  1.33% stalls)  98.23% L1 data cache
          3.10k cycles  13.50k instructions  34.90% retired LD/ST (  4.71k)

JSON.stringify + JSON.parse  527.05 ns/iter 575.48 ns       โ–ˆ    โ–ƒ
                      (367.58 ns โ€ฆ 2.30 ยตs) 732.21 ns      โ–ˆโ–ˆ    โ–ˆโ–‡
                    (  3.97  b โ€ฆ 383.93  b)  75.70  b โ–ƒโ–ƒโ–โ–‚โ–†โ–ˆโ–ˆโ–ˆโ–ƒโ–…โ–ˆโ–ˆโ–ˆโ–†โ–„โ–‚โ–‚โ–‚โ–โ–โ–
                  4.41 ipc (  1.07% stalls)  98.42% L1 data cache
          1.53k cycles   6.73k instructions  36.86% retired LD/ST (  2.48k)

JsonSchema.deepClone          62.08 ns/iter  65.56 ns     โ–ˆโ–…
                      (8.95 ns โ€ฆ 255.66 ns) 208.93 ns     โ–ˆโ–ˆโ–„
                    (  1.92  b โ€ฆ 214.18  b)  47.77  b โ–„โ–โ–โ–‡โ–ˆโ–ˆโ–ˆโ–…โ–‚โ–โ–โ–โ–โ–โ–โ–โ–โ–‚โ–‚โ–‚โ–
                  2.94 ipc (  1.29% stalls)  98.86% L1 data cache
         164.89 cycles  485.47 instructions  44.83% retired LD/ST ( 217.63)

                      Lodash โ”คโ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ–  868.72 ns
             structuredClone โ”คโ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ–  1.07 ยตs
 JSON.stringify + JSON.parse โ”คโ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ– โ–  527.05 ns
        JsonSchema.deepClone โ”ค 62.08 ns
                             โ””                                            โ”˜

                             โ”Œ                                            โ”
                                       โ•ท              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”    โ•ท
                      Lodash           โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค      โ”‚    โ”œโ”€โ”€โ”€โ”€โ”ค
                                       โ•ต              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”˜    โ•ต
                                                                   โ•ทโ”ฌโ”    โ•ท
             structuredClone                                       โ”œโ”‚โ”œโ”€โ”€โ”€โ”€โ”ค
                                                                   โ•ตโ”ดโ”˜    โ•ต
                                          โ•ท   โ”Œโ”€โ”ฌโ”€โ”     โ•ท
 JSON.stringify + JSON.parse              โ”œโ”€โ”€โ”€โ”ค โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”ค
                                          โ•ต   โ””โ”€โ”ดโ”€โ”˜     โ•ต
                             โ•ทโ”Œโ”ฌ    โ•ท
        JsonSchema.deepClone โ”œโ”คโ”‚โ”€โ”€โ”€โ”€โ”ค
                             โ•ตโ””โ”ด    โ•ต
                             โ””                                            โ”˜
                             8.95 ns           613.29 ns            1.22 ยตs

summary
  JsonSchema.deepClone
   8.49x faster than JSON.stringify + JSON.parse
   13.99x faster than Lodash
   17.23x faster than structuredClone

For a more detailed breakdown, see all the benchmark results.

Example

import { JsonSchema } from '@traversable/json-schema'

const Address = {
  type: 'object',
  required: ['street1', 'city'],
  properties: {
    street1: { type: 'string' },
    street2: { type: 'string' },
    city: { type: 'string' },
  }
} as const

const deepClone = JsonSchema.deepClone(Address)
const deepEqual = JsonSchema.deepEqual(Address)

const sherlock = { street1: '221 Baker St', street2: '#B', city: 'London' }
const harry = { street1: '4 Privet Dr', city: 'Little Whinging' }

const sherlockCloned = deepClone(sherlock)
const harryCloned = deepClone(harry)

deepEqual(sherlock, sherlockCloned) // => true
sherlock === sherlockCloned         // => false

deepEqual(harry, harryCloned)       // => true
harry === harryCloned               // => false

See also

JsonSchema.deepClone.writeable

JsonSchema.deepClone.writeable lets users derive a specialized "deep clone" function that works with values that have been already validated.

Compared to JsonSchema.deepClone, JsonSchema.deepClone.writeable returns the clone function in stringified ("writeable") form.

Example

import { JsonSchema } from '@traversable/json-schema'

const deepClone = JsonSchema.deepClone.writeable({
  type: 'object',
  required: ['firstName', 'address'],
  properties: {
    firstName: { type: 'string' },
    lastName: { type: 'string' },
    address: {
      type: 'object',
      required: ['street1', 'city', 'state'],
      properties: {
        street1: { type: 'string' },
        street2: { type: 'string' },
        city: { type: 'string' },
        state: { enum: ['AL', 'AK', 'AZ', '...'] }
      }
    }
  }
}, { typeName: 'User' })

console.log(deepClone) 
// Prints:
type User = {
  firstName: string
  lastName?: string
  address: {
    street1: string
    street2?: string
    city: string
    state: "AL" | "AK" | "AZ" | "..."
  }
}

function deepClone(prev: User): User {
  return {
    firstName: prev.firstName,
    ...(prev.lastName !== undefined && { lastName: prev.lastName }),
    address: {
      street1: prev.address.street1,
      ...(prev.address.street2 !== undefined && {
        street2: prev.address.street2,
      }),
      city: prev.address.city,
      state: prev.address.state,
    },
  }
}
import { JsonSchema } from '@traversable/json-schema'

const deepClone = JsonSchema.deepClone.writeable({
  $defs: {
    state: { enum: ['AL', 'AK', 'AZ', '...'] },
    address: {
      type: 'object',
      required: ['street1', 'city', 'state'],
      properties: {
        street1: { type: 'string' },
        street2: { type: 'string' },
        city: { type: 'string' },
        state: {
          $ref: '#/$defs/state'
        }
      }
    }
  },
  type: 'object',
  required: ['firstName', 'address'],
  properties: {
    firstName: { type: 'string' },
    lastName: { type: 'string' },
    address: {
      $ref: '#/$defs/address'
    }
  }
}, { typeName: 'User' })

console.log(deepClone)
// Prints:
type State = "AL" | "AK" | "AZ" | "..."
type Address = { 
  street1: string
  street2?: string
  city: string
  state: State 
}
type User = {
  firstName: string
  lastName?: string
  address: Address
}

function deepCloneState(value: State): State {
  return value
}
function deepCloneAddress(value: Address): Address {
  return {
    street1: value.street1,
    ...(value.street2 !== undefined && { street2: value.street2 }),
    city: value.city,
    state: deepCloneState(value.state),
  }
}
function deepClone(prev: User): User {
  return {
    firstName: prev.firstName,
    ...(prev.lastName !== undefined && { lastName: prev.lastName }),
    address: deepCloneAddress(prev.address),
  }
}

See also

JsonSchema.deepEqual

JsonSchema.deepEqual lets users derive a specialized "deep equal" function that works with values that have been already validated.

Because the values have already been validated, comparison times are significantly faster than alternatives like NodeJS.isDeepStrictEqual and Lodash.isEqual.

Performance comparison

Here's a Bolt sandbox if you'd like to run the benchmarks yourself.

                             โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                             โ”‚   Array (avg)  โ”‚  Object (avg)  โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  NodeJS.isDeepStrictEqual  โ”‚  40.3x faster  โ”‚  56.5x faster  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Lodash.isEqual            โ”‚  53.7x faster  โ”‚  60.1x faster  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

This article goes into more detail about what makes JsonSchema.deepEqual so fast.

Notes

  • Best performance
  • Works in any environment that supports defining functions using the Function constructor, including (as of May 2025) Cloudflare workers ๐ŸŽ‰

Example

import { JsonSchema } from '@traversable/json-schema'

const deepEqual = JsonSchema.deepEqual({
  type: 'object',
  required: ['street1', 'city'],
  properties: {
    street1: { type: 'string' },
    street2: { type: 'string' },
    city: { type: 'string' },
  }
})

deepEqual(
  { street1: '221 Baker St', street2: '#B', city: 'London' },
  { street1: '221 Baker St', street2: '#B', city: 'London' }
) // => true

deepEqual(
  { street1: '221 Baker St', street2: '#B', city: 'London' },
  { street1: '4 Privet Dr', city: 'Little Whinging' }
) // => false

See also

JsonSchema.deepEqual.writeable

JsonSchema.deepEqual.writeable lets users derive a specialized "deep equal" function that works with values that have been already validated.

Compared to JsonSchema.deepEqual, JsonSchema.deepEqual.writeable returns the deep equal function in stringified ("writeable") form.

Notes

  • Useful when you're consuming a set of JSON Schemas and writing all them to disc somewhere
  • Also useful for testing purposes or for troubleshooting, since it gives you a way to "see" exactly what the deepEqual functions are doing

Example

import { JsonSchema } from '@traversable/json-schema'

const deepEqual = JsonSchema.deepEqual.writeable({
  type: 'object',
  required: ['firstName', 'address'],
  properties: {
    firstName: { type: 'string' },
    lastName: { type: 'string' },
    address: {
      type: 'object',
      required: ['street1', 'city', 'state'],
      properties: {
        street1: { type: 'string' },
        street2: { type: 'string' },
        city: { type: 'string' },
        state: { enum: ['AL', 'AK', 'AZ', '...'] }
      }
    }
  }
}, { typeName: 'User' })

console.log(deepEqual)
// Prints:
type User = {
  firstName: string
  lastName?: string
  address: {
    street1: string
    street2?: string
    city: string
    state: "AL" | "AK" | "AZ" | "..."
  }
}

function deepEqual(l: User, r: User): boolean {
  if (l === r) return true
  if (l.firstName !== r.firstName) return false
  if ((l?.lastName === undefined || r?.lastName === undefined) && l?.lastName !== r?.lastName) return false
  if (l?.lastName !== r?.lastName) return false
  if (l.address !== r.address) {
    if (l.address.street1 !== r.address.street1) return false
    if (
      (l.address?.street2 === undefined || r.address?.street2 === undefined) &&
      l.address?.street2 !== r.address?.street2
    )
      return false
    if (l.address?.street2 !== r.address?.street2) return false
    if (l.address.city !== r.address.city) return false
    if (l.address.state !== r.address.state) return false
  }
  return true
}
import { JsonSchema } from '@traversable/json-schema'

const deepEqual = JsonSchema.deepEqual({
  $defs: {
    state: { enum: ['AL', 'AK', 'AZ', '...'] },
    address: {
      type: 'object',
      required: ['street1', 'city', 'state'],
      properties: {
        street1: { type: 'string' },
        street2: { type: 'string' },
        city: { type: 'string' },
        state: { $ref: '#/$defs/state' }
      }
    }
  },
  type: 'object',
  required: ['firstName', 'address'],
  properties: {
    firstName: { type: 'string' },
    lastName: { type: 'string' },
    address: { $ref: '#/$defs/address' }
  }
}, { typeName: 'User' })

console.log(deepEqual)
// Prints:
type State = "AL" | "AK" | "AZ" | "..."
type Address = {
  street1: string
  street2?: string
  city: string
  state: State 
}
type User = {
  firstName: string
  lastName?: string
  address: Address 
}

function deepEqualState(l: State, r: State): boolean {
  if (l !== r) return false
  return true
}
function deepEqualAddress(l: Address, r: Address): boolean {
  if (l.street1 !== r.street1) return false
  if ((l?.street2 === undefined || r?.street2 === undefined) && l?.street2 !== r?.street2) return false
  if (l?.street2 !== r?.street2) return false
  if (l.city !== r.city) return false
  if (!deepEqualState(l.state, r.state)) return false
  return true
}
function deepEqual(l: User, r: User): boolean {
  if (l === r) return true
  if (l.firstName !== r.firstName) return false
  if ((l?.lastName === undefined || r?.lastName === undefined) && l?.lastName !== r?.lastName) return false
  if (l?.lastName !== r?.lastName) return false
  if (!deepEqualAddress(l.address, r.address)) return false
  return true
}

See also

JsonSchema.toType

Convert a JSON Schema into its corresponding TypeScript type.

If the JSON Schema contains any references, the references will be compiled in a separate property of the return type.

Example

const UserType = JsonSchema.toType({
  type: 'object',
  required: ['firstName', 'address'],
  properties: {
    firstName: { type: 'string' },
    lastName: { type: 'string' },
    address: {
      type: 'object',
      required: ['street1', 'city', 'state'],
      properties: {
        street1: { type: 'string' },
        street2: { type: 'string' },
        city: { type: 'string' },
        state: { enum: ['AL', 'AK', 'AZ', '...'] }
      }
    }
  }
}, { typeName: 'User' })

console.log(UserType.result)
// Prints:
type User = {
  firstName: string
  lastName?: string
  address: {
    street1: string
    street2?: string
    city: string
    state: "AL" | "AK" | "AZ" | "..."
  }
}
import { JsonSchema, canonizeRefName } from '@traversable/json-schema'

const UserType = JsonSchema.toType({
  $defs: {
    state: { enum: ['AL', 'AK', 'AZ', '...'] },
    address: {
      type: 'object',
      required: ['street1', 'city', 'state'],
      properties: {
        street1: { type: 'string' },
        street2: { type: 'string' },
        city: { type: 'string' },
        state: { $ref: '#/$defs/state' }
      }
    }
  },
  type: 'object',
  required: ['firstName', 'address'],
  properties: {
    firstName: { type: 'string' },
    lastName: { type: 'string' },
    address: {
      $ref: '#/$defs/address'
    }
  }
}, { typeName: 'User' })

console.log([...Object.values(UserType.refs), UserType.result].join('\n'))
// Prints:
type State = "AL" | "AK" | "AZ" | "..."
type Address = {
  street1: string
  street2?: string
  city: string
  state: State
}
type User = {
  firstName: string
  lastName?: string
  address: Address
}

JsonSchema.fold

[!NOTE] JsonSchema.fold is an advanced API.

Use JsonSchema.fold to define a recursive traversal of a JSON Schema. Useful when building a schema rewriter.

What does it do?

Writing an arbitrary traversal with JsonSchema.fold is:

  1. non-recursive
  2. 100% type-safe

The way it works is pretty simple: if you imagine all the places in the JSON Schema specification that are recursive, those "holes" will be the type that you provide via type parameter.

Example

As an example, let's write a function called check that takes a JSON Schema, and returns a function that validates its input against the schema.

Here's how you could use JsonSchema.fold to implement it:

import { JsonSchema } from '@traversable/json-schema'

const isObject = (u: unknown): u is { [x: string]: unknown } => 
  !!u && typeof u === 'object' && !Array.isArray(u)

// transformed schema will be on the `result` property, transformed
// refs will be on the `refs` property
const { result: check } = JsonSchema.fold<(data: unknown) => boolean>(
  (schema) => { //             ๐™˜_______________________๐™˜
                //   this type will fill the "holes" in our schema
    switch (true) {
      case JsonSchema.isNull(schema): 
        return (data) => data === null
      case JsonSchema.isBoolean(schema): 
        return (data) => typeof data === 'boolean'
      case JsonSchema.isInteger(schema): 
        return (data) => Number.isSafeInteger(data)
      case JsonSchema.isNumber(schema): 
        return (data) => Number.isFinite(data)
      case JsonSchema.isArray(schema): 
        return (data) => Array.isArray(data) 
          && schema.every(schema.items)
          //                     ๐™˜___๐™˜
          //                     items: (data: unknown) => boolean
      case JsonSchema.isObject(schema): 
        return (data) => isObject(data) 
          && Object.entries(schema.properties).every(
            ([key, property]) => schema.required.includes(key) 
              //   ๐™˜______๐™˜ 
              //   property: (data: unknown) => boolean
              ? (Object.hasOwn(data, key) && property(data[key]))
              : (!Object.hasOwn(data, key) || property(data[key]))
          )
      default: return () => false
    }
  }
)

// Let's use `check` to create a predicate:
const isBooleanArray = check({
  type: 'array',
  items: { type: 'boolean' } 
})

// Using the predicate looks like this:
isBooleanArray([false])    // true
isBooleanArray([true, 42]) // false

That's it!

If you'd like to see a more complex example, here's how JsonSchema.check is actually implemented.

Theory

JsonSchema.fold is similar to, but more powerful than, the visitor pattern.

If you're curious about the theory behind it, its implementation was based on a 1991 paper called Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire.

See also

JsonSchema.Functor

[!NOTE] JsonSchema.Functor is an advanced API.

JsonSchema.Functor is the primary abstraction that powers @traversable/json-schema.

JsonSchema.Functor is a powertool. Most of @traversable/json-schema uses JsonSchema.Functor under the hood.

Compared to the rest of the library, it's fairly "low-level", so unless you're doing something pretty advanced you probably won't need to use it directly.

See also