How to manipulate my deep nested json in Javascript?

Viewed 144

Here is my nested object and I need to add "name", "children" for all parent nodes, and "name", "value" for last nodes:

{
    "myObj": {
        "a": {
            "b": {
                "testB1": "10",
                "testB2": "20",
            },
            "c": {
                "testC1": "43",
                "testC2": "",
                "testC3": [
                    {
                        "aa": "34",
                    },
                    {
                        "bb": "43",
                        "cc": "" 
                    },
                ],
                "testC4": {
                    "ee": {
                        "eee1": "11",
                        "eee2": [
                            {
                                "f": "50",
                            },
                            {
                                "i": "70",
                            }
                        ],
                        "eee3": "1"
                    },
                    "ee": "1"
                }
            }
        }
    }
}

Here is what I need (all names and the depth are random):

{name: "myObj", children: [
    {name: "a", children: [
            {name: "b", children:[
                {name: "testB1", value:"10"},
                {name: "testB2": value: "20"} 
            ]} ,
            {name: "c", children: [
                {name: "testC1": value: "43"},
                {name: "testC2": value: ""},
                {name: "testC3", children: [
                    {name: "aa", value: "34"},
                    {name: "bb", value: "43"},
                    {name: "cc", value: ""} 
                ]} ,
                {name: "testC4", children: [
                    {name: "ee", children: [
                        {name: "eee1", value: "11"},
                        {name: "eee2", children: [
                            {name: "f", value: "50"},
                            {name: "i", value: "70"}
                        ]} ,
                        {name: "eee3", value: "1"} 
                    }],
                    {name: "ee", value: "1"} 
                }]
            }]
        }]
    }]
}   

Here is my recursive function:

    function visitDescendants(obj, callback) {
        for (const [key, value] of Object.entries(obj)) {
            if (value && typeof value === "object") {
               let newObj = Object.entries(obj).map( ([key,value]) => ({name: key, children: [value]}) );
                // Recurse
                visitDescendants(value, callback);
            } else {
                let newObj = Object.entries(obj).map( ([key,value]) => ({name: key, value: value}) );
                callback(key, value);
            }
        }    
    }

I can iterate through my object, I can build my desire node, I print out all key values at the last level, but I cannot build the whole object:

visitDescendants(obj, (key, value) => {
    console.log(`${key}: ${value}`);
});
1 Answers

Trying to build such an object during that sort of visitation would require that your callback was stateful, or worse, that you were appending to an out-of-scope (probably global) object, neither of which appeals to me. I would rather simply take apart your object and put it back together as desired, in an immutable manner of course.

Here is an approach which does that. I have to make a guess about how you want arrays handled, as your specified output in that case is not legitimate. A single object cannot have multiple name, value or children properties. But I think this is a reasonable guess, and we can probably alter it easily enough if my guess is wrong.

const transform = (o) =>
  Array .isArray (o)
    ? o .map (transform)
    : Object .entries (o) .map (([k, v]) => ({
        name: k, 
        ... (Object (v) === v ? {children: transform (v)} : {value: v})
      }))

const input = {myObj: {a: {b: {testB1: "10", testB2: "20"}, c: {testC1: "43", testC2: "", testC3: [{aa: "34"}, {bb: "43", cc: ""}], testC4: {ee: {eee1: "11", eee2: [{f: "50"}, {i: "70"}], eee3: "1"}, ee: "1"}}}}}

console .log (transform (input))
.as-console-wrapper {max-height: 100% !important; top: 0}

The Object (v) === v is one of many ways to test if something is an object. typeof would work as well, as would some old-school techniques using Object .prototype .toString. I simply prefer this one. The rest of the code should be fairly clear. If we start with an array, we simply map our transformation function over its values. Otherwise, we take its entries as key-value pairs, and for each one build an object that has a name property with that key and either a children property or a value one based upon whether the child is an object or not.

If we could have arrays of scalar values, we might want to expand this slightly to handle non-array/non-object inputs. That version might look like this:

const transform = (o) =>
  Array .isArray (o)
    ? o .map (transform)
  : Object (o) === o
    ? Object .entries (o) .map (([k, v]) => ({
        name: k, 
        ... (Object (v) === v ? {children: transform (v)} : {value: v})
      }))
  : o

const input = {myObj: {foo: [1, 2, 3], a: {b: {testB1: "10", testB2: "20"}, c: {testC1: "43", testC2: "", testC3: [{aa: "34"}, {bb: "43", cc: ""}], testC4: {ee: {eee1: "11", eee2: [{f: "50"}, {i: "70"}], eee3: "1"}, ee: "1"}}}}}
//                     `- note this -'

console .log (transform (input))
.as-console-wrapper {max-height: 100% !important; top: 0}

Related