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

@json-schema-tools/traverse

v1.15.1

Published

This package exports a method that will traverse a JSON-Schema, calling a mutation function for each sub schema found. It is useful for building tools to work with JSON Schemas.

Readme

JSON Schema Traverse

This package exports a method that will traverse a JSON-Schema, calling a "mutation" function for each sub schema found. It is useful for building tools to work with JSON Schemas.

Features

  • circular reference detection & handling
  • synchronous unless a mutation callback returns a promise or thenable
  • doesn't touch the filesystem or make network requests itself
  • easily perform schema mutations while traversing
  • optional mutability (toggle updating original schema object)
  • returns JSONPaths as it traverses

Getting Started

npm install @json-schema-tools/traverse
const traverse = require("@json-schema-tools/traverse").default;
//import traverse from "@json-schema-tools/traverse"

const mySchema = {
  title: "baz",
  type: "object",
  properties: {
    foo: {
      title: "foo",
      type: "array",
      items: { type: "string" }
    },
    bar: {
      title: "bar",
      anyOf: [
        { title: "stringerific", type: "string" },
        { title: "numberoo", type: "number" }
      ]
    }
  }
};

traverse(mySchema, (schemaOrSubschema) => {
  console.log(schemaOrSubschema.title);
  return schemaOrSubschema;
});

By default, the callback must return a schema on every call, even when it only reads or logs the node. Return the supplied schema to keep it, or return another schema (including true or false) to replace it. Returning undefined or omitting the return throws a TypeError that includes the node path.

To allow callbacks that only inspect or edit nodes in place, set allowUndefinedReturn: true. An undefined return then keeps the node passed to the callback, including any in-place edits. With immutable traversal (the default), this keeps the working copy rather than the original input node. Returning undefined never removes a subschema.

traverse(mySchema, (schemaOrSubschema, isCycle, path) => {
  console.log(path);
}, { allowUndefinedReturn: true });

Async mutators

The same traverse function supports synchronous, asynchronous, and mixed callbacks. It returns a schema immediately when every invoked callback returns synchronously. Once a callback returns a promise or thenable, it returns a promise that resolves to the completed root schema, including replacements:

// Synchronous callbacks keep the synchronous API.
const result = traverse(mySchema, node => node);

// Awaiting works for both synchronous and asynchronous traversals.
const enriched = await traverse(mySchema, async node => {
  const title = await lookupTitle(node);
  return typeof node === "boolean" ? node : { ...node, title };
});

// Only visits needing asynchronous work return a promise.
const mixed = traverse(mySchema, node => {
  if (typeof node === "object" && node["x-model"]) {
    return enrichModel(node);
  }
  return node;
});

Traversal awaits each callback before continuing to the next node. By default, parents receive their children's resolved replacements. With bfs: true, the parent callback finishes before child discovery starts. A preorder replacement of true or false ends traversal of that node's children. Callback order, paths, custom subschemas, and cycle/shared-reference handling are preserved. An async callback that is never invoked (for example, on a skipped leaf root) does not cause a promise return. The additionalSubschemas selector itself remains synchronous.

Returning a promise that resolves to undefined follows the same allowUndefinedReturn policy as a synchronous undefined return. A failure before the first promise throws synchronously; failures after traversal becomes asynchronous reject the returned promise and stop further callbacks. With mutable: true, edits completed before a failure are not rolled back.

TypeScript infers JSONSchema for synchronous callbacks and JSONSchema | Promise<JSONSchema> for callbacks that can return promises. The union also covers traversals where every async callback is skipped. The exported MutationFunction accepts both kinds of callback; SyncMutationFunction keeps the synchronous return type for explicitly annotated callbacks.

Advanced Options

traverse accepts an optional options object as the third argument. Some useful flags include:

  • pathFormat - "jsonpath" (default) or "jsonpointer" for callback paths
  • bfs - call the callback before visiting a schema's children (preorder). Despite its name, this currently uses depth-first traversal, not level-by-level breadth-first traversal. The default calls the callback after the children.
  • allowUndefinedReturn - keep the callback's node when it returns undefined or nothing. Defaults to false, which throws an error for undefined returns.
  • skipFirstMutation - do not call the mutation function on the root schema
  • additionalSubschemas - select custom subschemas using paths relative to each object schema
  • mergeNotMutate - merge the mutation result back into the original schema
traverse(mySchema, (schemaOrSubschema) => {
  console.log(schemaOrSubschema.title);
  return schemaOrSubschema;
}, {
  bfs: true,
  skipFirstMutation: true,
  mergeNotMutate: true,
});

Custom subschemas

Use additionalSubschemas to declare schema locations under custom keywords. Each descriptor has a non-empty path of literal property names and array indices, relative to the object schema passed to the selector:

const schema = {
  type: "object",
  properties: { ordinary: { type: "string" } },
  "x-models": {
    response: { type: "array", items: { type: "number" } },
    disabled: false,
  },
};

traverse(schema, (subschema, isCycle, path, parent) => {
  console.log(path);
  return subschema;
}, {
  additionalSubschemas(node) {
    return Object.keys(node["x-models"] ?? {}).map(name => ({
      path: ["x-models", name],
    }));
  },
});

This visits the ordinary property, both custom schemas, the response's items, and the root. The x-models map is a container, so it does not receive a mutation callback. Custom children use the same mutation, path formatting, parent schema, and cycle/shared-reference behavior as built-in children. Array entries can be selected with paths such as ["x-models", "responses", 0].

The selector runs once for each visited object schema, on the input node after the preorder callback (when bfs: true) and before its children are mutated. Boolean schemas do not invoke the selector. Treat the selector's input as read-only. Custom children are visited recursively; each object child can select more custom children. Built-in children are discovered first, followed by custom children in selector order. Overlapping ancestor/descendant selections are forwarded through the ancestor so each location is visited once.

Duplicate paths, including overlaps with built-in locations, do not cause extra visits. Numeric array indices and their string equivalents identify the same location. Paths are independent of pathFormat; names containing dots, slashes, or tildes are literal names, not JSONPath or JSON Pointer expressions.

Every target must exist as an own property and be an object or boolean schema. Missing paths, invalid segments, and targets such as arrays, strings, numbers, or null throw a TypeError with the location. Intermediate objects and arrays are copied as needed in immutable mode. Unselected extension data is preserved without being traversed. Selecting a value explicitly declares it to be a schema; this option does not validate its contents or dereference $ref values.

The AdditionalSubschema and AdditionalSubschemas types are exported for typed selectors. Omit the option to retain the existing traversal behavior.

API Docs

The full TypeDoc generated API documentation is available at https://json-schema-tools.github.io/traverse/.

Contributing

How to contribute, build and release are outlined in CONTRIBUTING.md, BUILDING.md and RELEASING.md respectively. Commits in this repository follow the CONVENTIONAL_COMMITS.md specification.

Callback paths

The callback's third argument is a JSONPath identifying the visited schema. Simple member names use dot notation ($.properties.foo), array indices use brackets ($.items[0]), and other names use JSON-escaped double-quoted brackets ($.properties["a.b"]). The root path is $.

Select JSON Pointer with pathFormat: "jsonpointer":

traverse(mySchema, (schema, isCycle, path) => {
  console.log(path); // e.g. /properties/foo/items
  return schema;
}, { pathFormat: "jsonpointer" });

JSON Pointer paths use the RFC 6901 string representation: the root is "", array indices are separate tokens (/items/0), and member names escape ~ as ~0 and / as ~1. An empty property name is preserved (/properties/). These paths identify locations within the input schema; they are not instance paths or URI fragments. Omitting pathFormat preserves JSONPath output.