Skip to content
Advertisement

How to walk through the object/array in javascript by layers

I am converting the nested data object to the arrays, for the UI Library to show the relationship between the data.

Original

// assume that all object key is unique


{
"top":{
  "test":{
     "hello":"123"
   },
  "test2":{
     "bye":"123"
     "other":{
        ...
         ... 
          ...
      }
   }
}
}

Preferred Result

[
  {
    id:"top",
    parent: null,
  },
  {
    id:"test",
    parent: "top",
  },
  {
    id:"hello",
    parent: "test",
  },
  {
    id:"test2",
    parent: "top",
  },
]

To do this, I write the code like this:

const test = []
const iterate = (obj, parent = null) => {
    Object.keys(obj).forEach(key => {
        const id = typeof obj[key] === 'object' ? key : obj[key]
        const loopObj = {
            id,
            parent
        }
        test.push(loopObj)

        if (typeof obj[key] === 'object') {
            iterate(obj[key], id)
        }
    })
}

iterate(data)
console.log(test) // Done!!

It works. However, I miss one important things, the library need the layers from the original data, to determine the type/ what function to do.

// The key name maybe duplicated in different layer

{
"top":{ // Layer 1
  "test":{  // Layer 2
     "hello":"123", // Layer 3
     "test":"123" // Layer 3
      // Maybe many many layers...
   }
}
}

[
  {
    id:"top",
    display:"0-top",
    parent: null,
    layer: 0
  },
  {
    id: "1-top-test", // To prevent duplicated id, `${layer}-${parentDisplay}-${display}`
    display:"test",
    parent: "0-top",
    parentDisplay: "top",
    layer: 1
  },
 {
    id: "3-test-test", // To prevent duplicated id,`${layer}-${parentDisplay}-${display}`
    display:"test",
    parent: "2-top-test",
    parentDisplay: "test",
    layer: 3
  }
]

Editing the display or id format is very simple, just edit the function and add the field, but I don’t know how to get the layer easily.

I tried to add the let count = 0 outside and do count++ when iterate function called.But I realized that it hit when the object detected, no by layers.

The original data may be very big, So I think editing the original data structure or searching the parent id in the test[] every loop may be not a good solution.

Is there any solution to do this?

Advertisement

Answer

Just add the current depth as an argument that gets passed down on every recursive call (as well as the parent name).

const input = {
  "top":{
    "test":{
       "hello":"123"
     },
    "test2":{
       "bye":"123",
       "other":{
        }
     }
  }
};
const iterate = (obj, result = [], layer = 0, parentId = null, parentDisplay = '') => {
    Object.entries(obj).forEach(([key, value]) => {
        const id = `${layer}-${key}`;
        result.push({
            id,
            display: key,
            parentId,
            parentDisplay,
            layer,
        });
        if (typeof value === 'object') {
            iterate(value, result, layer + 1, id, key);
        }
    });
    return result;
}

console.log(iterate(input));

That said, your desired approach can still produce duplicate entries, if there exist two objects at the same level, with different grandparent objects, but whose parent objects use the same key, eg:

const input = {
  "top1":{
    "test":{
       "hello":"123"
     },
   },
   "top2": {
     "test": {
       "hello":"123"
     }
   }
};

const input = {
  "top1":{
    "test":{
       "hello":"123"
     },
   },
   "top2": {
     "test": {
       "hello":"123"
     }
   }
};
const iterate = (obj, result = [], layer = 0, parentId = null, parentDisplay = '') => {
    Object.entries(obj).forEach(([key, value]) => {
        const id = `${layer}-${key}`;
        result.push({
            id,
            display: key,
            parentId,
            parentDisplay,
            layer,
        });
        if (typeof value === 'object') {
            iterate(value, result, layer + 1, id, key);
        }
    });
    return result;
}

console.log(iterate(input));

If that’s a problem, consider passing down the entire accessor string needed to access the property – eg top1.test.hello and top2.test.hello, which is guaranteed to be unique.

const input = {
  "top1":{
    "test":{
       "hello":"123"
     },
   },
   "top2": {
     "test": {
       "hello":"123"
     }
   }
};
const iterate = (obj, result = [], parentAccessor = '') => {
    Object.entries(obj).forEach(([key, value]) => {
        const accessor = `${parentAccessor}${parentAccessor ? '.' : ''}${key}`;
        result.push({
            id: key,
            accessor,
        });
        if (typeof value === 'object') {
            iterate(value, result, accessor);
        }
    });
    return result;
}

console.log(iterate(input));
Advertisement