Home > Blockchain >  Javascript recursive function on nested object
Javascript recursive function on nested object

Time:07-09

We have the following kind of object:

const obj = {
    a: { "=": 0 },
    b: {
        c: { "=": 1 },
        d: {
            e: { "=": 2 },
            f: {
                g: { "=": 3 }
            }
        }    
    }

}

I am trying to write a function that returns an array with the object keys, including the nested ones, but groups them if they are nested and their values is not { "=": "something" }. To better explain I'll write a sample output desired for the object above:

["a", "b.c", "b.d.e", "b.d.f.g"]

All I have achieved for now is a function that only traverses the first level, but I admit I got stuck on the recursive part where the same function will apply on every level of the object until it reaches an entry of type { "=": "something" }. Follows my actual code:

function nestedObject(obj) {
    const operator = "="
    const attributes = []
    void (function traverse(obj) {
        if (isObject(obj)) { // <-- isObject(obj) just checks if typeof obj == "object", !Array.isArray(obj), obj !== null
            for (const [k,v] of Object.entries(obj)) {
                let attr = k
                for (const [k_,v_] of Object.entries(v)) {
                    if (operator !== k_) {
                        attr  = "."   k_
                        // here is where the recursive part should start 
                        // but I didn't figure out yet how to make it right. 
                        // Of course calling traverse(v_) doesn't give the desired result.
                    }
                    attributes.push(attr)
                }
            }
        }
    })(obj)
    console.log(attributes) // <-- will output ["a", "b.c", "b.d"]
}

I hope I did explain well enough. I don't even know if this is possible, but I think it is. Would love to hear some ideas.

CodePudding user response:

Here's a small generator function that, given an object, recursively yields pairs like [ array-of-nested-keys, value ]:

function* enumRec(obj, keys=[]) {
    yield [keys, obj];
    if (obj && typeof obj === 'object')
        for (let [k, v] of Object.entries(obj))
            yield *enumRec(v, keys.concat(k))
}

To solve the problem at hand, iterate enumRec and collect keys that end with a =:

for (let [keys, _] of enumRec(obj)) {
    if (keys.at(-1) === '=')
        console.log(keys.slice(0, -1).join('.'))
}

CodePudding user response:

I keep handy some utility functions to make such coding easier. One of them takes an object and lists all the leaf paths in that object, as an array of keys. Building your function atop that becomes simply a matter of removing trailing = parts and joining the results with dots:

const getPaths = (obj) =>
  Object (obj) === obj
    ? Object .entries (obj) .flatMap (([k, v]) => getPaths (v) .map (p => [k, ...p]))
    : [[]]

const nestedObject = (obj) =>
  getPaths (obj) 
    .map (p => p .at (-1) == '=' ? p .slice (0, -1): p)
    .map (p => p .join ('.'))

const obj = {a: {"=": 0}, b: {c: {"=": 1}, d: {e: {"=": 2}, f: {g: {"=": 3}}}}}

console .log (nestedObject (obj))

This is a slightly less sophisticated version of getPaths than I usually write. Usually I distinguish between numeric array indices and string object keys, but that's not relevant here, so this version is simplified.

The main function should be clear enough on top of that.

Note that this does not prohibit internal = keys, so if, parallel to g in your input, you also had an h with an = key, like this:

            f: {
                g: { "=": 3 },
                h: { "=": {i: { "=": 4}}}
            }

we would yield this result:

["a", "b.c", "b.d.e", "b.d.f.g", "b.d.f.h.=.i"]

If you also wanted to remove those, you could add this between the two map calls:

    .filter (p => ! p.includes ('='))
  • Related