Skip to Content
  • Website
Codsen
  • Home
  • Open Source
  • Articles
  • About

prevOpen Source→ast-monkeynext

ast-monkey9.2.1

Traverse and edit AST

Downloads per monthChangelogMIT Licenselibera manifesto
  • the top
  • Installation
  • Quick Take
  • Examples
  • THE CHALLENG…
  • IDEA
  • API — FIND()
  • API — GET()
  • API — SET()
  • API — DROP()
  • API — DEL()
  • API — ARRAYFIRSTONL…
  • API — TRAVERSE()
  • API — TYPES
  • Changelog

Installation

Quick Take

Examples

  • Delete matches from arrays only
  • Delete every object key of a given name
  • Delete nodes by value
  • Drop a node by its traversal index
  • Find nodes matching both a key and a value
  • Find nodes by value alone
  • Search array elements only
  • Search object properties only
  • Get a node by its traversal index
  • Keep only the first element of every array
  • Set a node's value by its traversal index
  • Set a node using key instead of val
  • Traverse a tree using the re-exported traverse

The challenge

Operations on AST’s — Abstract Syntax Trees — or anything deeply nested are difficult.

The main challenge is going “up the branch” — querying the parent and sibling nodes.

Second challenge, AST’s get VERY BIG very quickly. A single tag, <td>a</td>, 10 characters produced 398 characters of AST above. Enormous inputs are very hard to reason about, especially to troubleshoot, printed trees don’t fit into screen.

The “Going up” is often solved by putting circular references in the parsed tree, like "parent": "[Circular ~.0]",. The first drawback of using circular references is that it’s not standard JSON, you can’t even JSON.stringify (specialised stringification packagesopens in a new tab do exist) — everything from the algorithm up to the unit test runner are affected. The second drawback of circular references is that while they make it easier to query things, they also make it harder to amend things — you have to amend “circular extras” as well (or hope renderer will be OK, but that’s only for small operations).

This program doesn’t rely on circular references. It uses indexing of “breadcrumb” paths. For example, you traverse and find that node you want is index 58, whole path being [2, 14, 16, 58]. You save the path down. After the traversal is done, you fetch the monkey to delete the index 58. You can also use a for loop on breadcrumb index array, [2, 14, 16, 58] and fetch and check parent 16 and grandparent 14. Lots of possibilities. Function find() searches using key or value or both, and function get() searches using a known index. That’s the strategy.

Idea

Conceptually, we use two systems to mark paths in AST:

  1. Our unique, number-based indexing system — each encountered node is numbered, for example, 58 (along with “breadcrumb” path, an array of integers, for example, [2, 14, 16, 58]). If you know the number you can get monkey to fetch you the node at that number or make amends on it.
  2. object-path notation, as in foo.1.bar (instead of foo[1].bar). The dot marking system is also powerful, it is used in many of our programs, although it has some shortcomings (no dots in key namesopens in a new tab, for example).

Traversal function will report both ways.

API — find()

This function can search inside JSON-like data structures for plain objects by key or by value or by both and return the indexes path to an each finding.

The function find() is imported like this:

It takes two input arguments:

Input argumentTypeObligatoryDescription
input
Type: Whatever
Obligatory: yes
inputWhateveryesJSON-like data structure.
options
Type: Plain object
Obligatory: yes
optionsPlain objectyesObligatory Options Object.

The Obligatory Options Object has the following shape:

KeyTypeObligatoryDescription
key
Type: String
Obligatory: at least one, key or val
keyStringat least one, key or valIf you want to search by a plain object’s key, put it here.
val
Type: Whatever
Obligatory: at least one, key or val
valWhateverat least one, key or valIf you want to search by a plain object’s value, put it here.
only
Type: undefined or null or "any" or "array" or "object"
Obligatory: no (defaults to any)
onlyundefined or null or "any" or "array" or "object"no (defaults to any)You can specify, to search only within arrays, objects or anything.

Either opts.key or opts.val or both must be present. If both are missing, find() will throw an error.

Output

The output will be an array, comprising of zero or more plain objects in the following format:

A finding object’s keyTypeDescription
index
Type: Integer number
indexInteger numberThe index of the finding. It’s also the last element of the path array.
key
Type: String
keyStringThe found object’s key
val
Type: Whatever or null
valWhatever or nullThe found object’s value (or null if it’s a key of an array)
path
Type: Array
pathArrayThe found object’s path: indexes of all its parents, starting from the topmost. The found key/value pair’s address will be the last element of the path array.

If a finding is an element of an array, the val will be set to null.

A use example

Find out, what is the path to the key that equals ‘b’.

import { find, get, set, drop, del, arrayFirstOnly, traverse } from "ast-monkey";
const input = ["a", [["b"], "c"]];
const key = "b";
const result = find(input, { key: key });
console.log("result = " + JSON.stringify(result, null, 4));
// => [
//      {
//        index: 4,
//        key: 'b',
//        val: null,
//        path: [2, 3, 4]
//      }
//    ]

Once you know that the path is [2, 3, 4], you can iterate its parents, get()-ing indexes number 3 and 2 and perform operations on it. The last element in the findings array is the finding itself.

This method is the most versatile of the ast-monkey because you can go “up the AST tree” by querying its array elements backwards.

API — get()

Use method get() to query AST trees by branch’s index (a numeric id). You would get that index from a previously performed find() or you can pick a number manually.

The get() is used on each element of the findings array (which you would get after performing find()). Then, depending on your needs, you would write the particular index over using set() or delete it using drop().

The function get() is imported like this:

It’s a function which takes two input arguments:

Input argumentTypeObligatoryDescription
input
Type: Whatever
Obligatory: yes
inputWhateveryesJSON-like data structure.
opts
Type: Plain object
Obligatory: yes
optsPlain objectyesObligatory Options Object.

The Obligatory Options Object has the following shape:

KeyTypeObligatoryDescription
index
Type: Number or number-as-string
Obligatory: yes
indexNumber or number-as-stringyesIndex number of piece of AST you want the monkey to retrieve for you.
only
Type: undefined or null or "any" or "array" or "object"
Obligatory: no (defaults to any)
onlyundefined or null or "any" or "array" or "object"no (defaults to any)You can specify, to search only within arrays, objects or anything.

Output

The get() returns object, array or null, depending what index was matched (or not).

A use example

If you know that you want an index number two, you can query it using get():

import { find, get, set, drop, del, arrayFirstOnly, traverse } from "ast-monkey";
const input = {
  a: {
    b: "c",
  },
};
const index = 2;
const result = get(input, { index: index });
console.log("result = " + JSON.stringify(result, null, 4));
// => {
//      b: 'c'
//    }

In practice, you would query a list of indexes programmatically using a for loop.

API — set()

Use set() to overwrite a piece of an AST when you know its index.

The function set() is imported like this:

It’s a function which takes two input arguments:

Input argumentTypeObligatoryDescription
input
Type: Whatever
Obligatory: yes
inputWhateveryesJSON-like data structure.
options
Type: Object
Obligatory: yes
optionsObjectyesObligatory Options Object.

The Obligatory Options Object has the following shape:

KeyTypeObligatoryDescription
index
Type: Number or number-as-string
Obligatory: yes
indexNumber or number-as-stringyesIndex of the piece of AST to find and replace
val
Type: Whatever
Obligatory: yes
valWhateveryesValue to replace the found piece of AST with

Output

Function returns an amended cloned input.

A use example

Let’s say you identified the index of a piece of AST you want to write over:

import { find, get, set, drop, del, arrayFirstOnly, traverse } from "ast-monkey";
const input = {
  a: { b: [{ c: { d: "e" } }] },
  f: { g: ["h"] },
};
const index = "7";
const val = "zzz";
const result = set(input, { index: index, val: val });
console.log("result = " + JSON.stringify(result, null, 4));
// => {
//      a: {b: [{c: {d: 'e'}}]},
//      f: {g: 'zzz'}
//    }

API — drop()

Use drop() to delete a piece of an AST with a known index.

The function drop() is imported like this:

It takes two input arguments:

Input argumentTypeObligatoryDescription
input
Type: Whatever
Obligatory: yes
inputWhateveryesJSON-like data structure.
options
Type: Object
Obligatory: yes
optionsObjectyesObligatory Options Object.

The Obligatory Options Object has the following shape:

KeyTypeObligatoryDescription
index
Type: Number or number-as-string
Obligatory: yes
indexNumber or number-as-stringyesIndex number of piece of AST you want the monkey to delete for you.

Output

Function returns an amended cloned input.

A use example

Let’s say you want to delete the piece of AST with an index number 8. That’s 'h':

import { find, get, set, drop, del, arrayFirstOnly, traverse } from "ast-monkey";
const input = {
  a: { b: [{ c: { d: "e" } }] },
  f: { g: ["h"] },
};
const index = "8"; // can be integer as well
const result = drop(input, { index: index });
console.log("result = " + JSON.stringify(result, null, 4));
// => {
//      a: {b: [{c: {d: 'e'}}]},
//      f: {g: []}
//    }

API — del()

Use del() to delete all chosen key/value pairs from all objects found within an AST, or all chosen elements from all arrays.

The function del() is imported like this:

It takes two input arguments:

Input argumentTypeObligatoryDescription
input
Type: Whatever
Obligatory: yes
inputWhateveryesJSON-like data structure.
opts
Type: Plain object
Obligatory: yes
optsPlain objectyesObligatory Options Object. See below.

The Obligatory Options Object has the following shape:

KeyTypeObligatoryDescription
key
Type: String
Obligatory: at least one, key or val
keyStringat least one, key or valAll keys in objects or elements in arrays will be selected for deletion
val
Type: Whatever
Obligatory: at least one, key or val
valWhateverat least one, key or valAll object key/value pairs having this value will be selected for deletion
only
Type: String
Obligatory: no (if not given, will default to any)
onlyStringno (if not given, will default to any)You can specify, to delete key/value pairs of an object or only elements on an array.

If you set only key, any value will be deleted as long as key matches. Same with specifying only val. If you specify both, both will have to match; otherwise, key/value pair (in objects) will not be deleted. Since arrays won’t have any values, no elements in arrays will be deleted if you set both key and val.

Output

Function returns an amended cloned input.

A use example

Let’s say you want to delete all key/value pairs from objects that have a key equal to ‘c’. Value does not matter.

import { find, get, set, drop, del, arrayFirstOnly, traverse } from "ast-monkey";
const input = {
  a: { b: [{ c: { d: "e" } }] },
  c: { d: ["h"] },
};
const key = "c";
const result = del(input, { key: key });
console.log("result = " + JSON.stringify(result, null, 4));
// => {
//      a: {b: [{}]}
//    }

API — arrayFirstOnly()

arrayFirstOnly() will take an input (whatever), if it’s traversable, it will traverse it, leaving only the first element within each array it encounters.

The function arrayFirstOnly() is imported like this:

It takes one input argument:

Input argumentTypeObligatoryDescription
input
Type: Whatever
Obligatory: yes
inputWhateveryesJSON-like data structure.

Output

Function returns an amended cloned input.

A use example

import { find, get, set, drop, del, arrayFirstOnly, traverse } from "ast-monkey";
const input = [
  {
    a: "a",
  },
  {
    b: "b",
  },
];
const result = arrayFirstOnly(input);
console.log("result = " + JSON.stringify(result, null, 4));
// => [
//      {
//        a: 'a'
//      }
//    ]

In practice, it’s handy when you want to simplify the data objects. For example, all our email templates have content separated from the template layout. Content sits in index.json file. For dev purposes, we want to show, let’s say two products in the shopping basket listing. However, in a production build, we want to have only one item, but have it sprinkled with back-end code (loop logic and so on). This means, we have to take data object meant for a dev build, and flatten all arrays in the data, so they contain only the first element. ast-monkey comes to help.

API — traverse()

The function traverse() is imported like this:

traverse() is re-exported from an npm package ast-monkey-traverse. For detailed API description, see its documentation.

API — types

This package is written in TypeScript and exports the following types:

TypeDescription
Finding
Type: Finding
FindingOne result from find() — its index, key, val and the breadcrumb path.
FindOpts
Type: FindOpts
FindOptsThe Options Object of find().
GetOpts
Type: GetOpts
GetOptsThe Options Object of get().
SetOpts
Type: SetOpts
SetOptsThe Options Object of set().
DropOpts
Type: DropOpts
DropOptsThe Options Object of drop().
DelOpts
Type: DelOpts
DelOptsThe Options Object of del().
JsonValue
Type: JsonValue
JsonValueAny JSON-representable value — what every function here accepts and returns.
JsonObject
Type: JsonObject
JsonObjectAn object whose values are all JsonValues.
JsonArray
Type: JsonArray
JsonArrayAn array of JsonValues.
import type { DelOpts, DropOpts, Finding, FindOpts, GetOpts, JsonArray, JsonObject, JsonValue, SetOpts } from "ast-monkey";

Changelog

Open Changelog
↑ back to top
prev next

Copyright

All rights reserved © Roy Revelt 2026
All our open source packages are under MIT licenceopens in a new tab

Activities

🐛 See a bug? Raise an issueopens in a new tab
💘 Check out the Indiewebopens in a new tab and Libera manifestoopens in a new tab