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]);
}
});
}
@alexleventer
Copy link

alexleventer commented Jun 29, 2017

const traverse = obj => {
  _.forOwn(obj, (val, key) => {
    if (_.isArray(val)) {
      val.forEach(el => {
        traverse(el);
      });
    } else if (_.isObject(val)) {
      traverse(val);
    } else {
      // do something with leaf node
    }
  });
};

I came up with this, which is a little different. Am I missing something?

@eldoy
Copy link

eldoy commented Apr 29, 2018

Pure javascript solution:

const traverse = (obj) => {
  for (let k in obj) {
    if (obj[k] && typeof obj[k] === 'object') {
      traverse(obj[k])
    } else {
      // Do something with obj[k]
    }
  }
}

@YuriGor
Copy link

YuriGor commented Oct 29, 2018

https://www.npmjs.com/package/deepdash

_.eachDeep(obj, (value, key, path, depth, parent, parentKey, parentPath) => {
  //do
});

@joshuaquek
Copy link

Hey man, thanks for the traversal algorithm, the pure javascript one. It really helped a lot!

@jzabala
Copy link

jzabala commented Nov 9, 2019

Pure javascript solution:

const traverse = (obj) => {
  for (let k in obj) {
    if (obj[k] && typeof obj[k] === 'object') {
      traverse(obj[k])
    } else {
      // Do something with obj[k]
    }
  }
}

Thanks @fugroup .

@fabioricali
Copy link

fabioricali commented Jul 26, 2020

const traverse = (obj) => {
for (let k in obj) {
if (obj[k] && typeof obj[k] === 'object') {
traverse(obj[k])
} else {
// Do something with obj[k]
}
}
}

const traverse = (obj) => {
    for (let k in obj) {
        if (obj.hasOwnProperty(k) && obj[k] && typeof obj[k] === 'object') {
            traverse(obj[k])
        } else {
            // Do something with obj[k]
        }
    }
}

@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