Skip to content

Instantly share code, notes, and snippets.

@tushar-borole
Forked from steinfletcher/traverse.js
Last active May 16, 2023 17:37
Show Gist options
  • Save tushar-borole/567c1d22ca8d5498cbc0 to your computer and use it in GitHub Desktop.
Save tushar-borole/567c1d22ca8d5498cbc0 to your computer and use it in GitHub Desktop.
Object tree traversal in javascript (with lodash)
var data = {
"name": "root",
"contents": [
{
"name": "A",
"contents": [
{
"name": "fileA1",
"contents": []
}
]
},
{
"name": "B",
"contents": [
{
"name": "dirB1",
"contents": [
{
"name": "fileBB1",
"contents": []
}
]
},
{
"name": "fileB1",
"contents": []
}
]
}
]
};
traverse(data);
function traverse(obj) {
_.forIn(obj, function (val, key) {
console.log(key, val);
if (_.isArray(val)) {
val.forEach(function(el) {
if (_.isObject(el)) {
traverse(el);
}
});
}
if (_.isObject(key)) {
traverse(obj[key]);
}
});
}
@hpl002
Copy link

hpl002 commented Nov 26, 2020

Pure JS function to execute callback on every match

  async deepTraversal(obj, pKey, callback) {
    Object.keys(obj).forEach((vKey) => {
      if (vKey === pKey) {
        callback(obj[vKey]);
      }
      if (typeof obj[vKey] === "object") {
        iterate(obj[vKey]);
      }
    });
  }

@tremendus
Copy link

iterate(obj[vKey]);

iterate is not defined. deepTraversal perhaps .... otherwise there is no recursion ;)

@serg06
Copy link

serg06 commented Aug 22, 2021

Here's a funny one which works for serializable/deserializable types:

function traverse(obj, f) {
    return JSON.parse(JSON.stringify(obj), f);
}

JSON.parse calls f on every key-value pair, and what you return is what the new value will be.

@Alvis-Li
Copy link

Here's a funny one which works for serializable/deserializable types:

function traverse(obj, f) {
    return JSON.parse(JSON.stringify(obj), f);
}

JSON.parse calls f on every key-value pair, and what you return is what the new value will be.

👍👍👍

@sykire
Copy link

sykire commented May 16, 2023

export const DELETE = Symbol('DELETE');

/**
 * Traverse an object and apply a transformation function to each value. 
 * The transformation function is called with the value, the key and the key path.
 * 
 * The transformation function can return a new value or the same value. 
 * If the transformation function returns a new value, the value in the object is replaced with the new value.
 * 
 * The transformation function can return an object.
 * If the transformation function returns an object, the object is traversed as well.
 * 
 * Warning: The transformation could loop infinitely if it always returns an object.
 * 
 * @param obj  { object } - the object to traverse
 * @param transform { (value: unknown, key: string, keyPath: string) => unknown } - a function that transforms the value, the key and the key path
 */
export async function traverse(obj, transform) {
    /**
     * @type { object[] }
     */
    const objectStack = [obj];

    /**
     * @type { string[] }
     */
    const keysStack = []

    while (objectStack.length > 0) {
        const obj = objectStack.pop();
        if (obj) {
            for (const key in obj) {
                keysStack.push(key);
                const keyPath = keysStack.join('.');
                const value = obj[key];
                const transformed = transform(value, key, keyPath);

                if(transformed === DELETE){
                    delete obj[key];
                    keysStack.pop();
                    continue;
                }

                if (transformed !== value) {
                    obj[key] = transformed;
                }

                if (typeof transformed === 'object' && transformed !== null) {
                    objectStack.push(transformed);
                } else {
                    keysStack.pop();
                }
            }
        }
    }
}

/**
 * Traverse an object and apply a transformation function to each value. 
 * The transformation function is called with the value, the key and the key path.
 * 
 * The transformation function can return a new value or the same value. 
 * If the transformation function returns a new value, the value in the object is replaced with the new value.
 * 
 * The transformation function can return an object.
 * If the transformation function returns an object, the object is traversed as well.
 * 
 * The transformation function can return a promise.
 * If the transformation function returns a promise, the promise is awaited and the resolved value is used.
 * 
 * Warning: The transformation could loop infinitely if it always returns an object.
 * 
 * @param obj { object } - the object to traverse
 * @param transform { (value: unknown, key: string, keyPath: string) => unknown | Promise<unknown> } - a function that transforms the value, the key and the key path
 * @returns
 **/
export async function traverseAsync(obj, transform) {
    /**
     * @type { object[] }
     */
    const objectStack = [obj];

    /**
     * @type { string[] }
     */
    const keysStack = []

    while (objectStack.length > 0) {
        const obj = objectStack.pop();
        if (obj) {
            for (const key in obj) {
                keysStack.push(key);
                const keyPath = keysStack.join('.');
                const value = obj[key];

                const awaitedValue = await value;

                const transformed = await transform(awaitedValue, key, keyPath);
                
                if(transformed === DELETE){
                    delete obj[key];
                    keysStack.pop();
                    continue;
                }

                if(value !== transformed){
                    obj[key] = transformed;
                }

                if (typeof transformed === 'object' && transformed !== null) {
                    objectStack.push(transformed);
                } else {
                    keysStack.pop();
                }
            }
        }
    }
}

const obj = { a: 1, b: { c: [{ a: 2 }, { d: 20}] } }

traverse(obj, (value, key, keyPath) => {
    if(keyPath === 'a'){
        return DELETE;
    }

    return value
})

obj


const asyncObj = { a: 1, b: { c: Promise.resolve([{ a: 2 }]) } }

traverseAsync(asyncObj, (value, key, keyPath) => {
    if(keyPath === 'a'){
        return DELETE;
    }

    return value
}).then(() => {
    asyncObj
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment