Hirvesh
Hirvesh

Reputation: 7982

Inserting/navigating nested JavaScript object/array

I've got this data:

const items = [
  {
    _id: 0,
    content: 'Item 1 something',
    note: 'Some note for item 1'
  },
  {
    _id: 5,
    content: 'Item 1.1 something',
    note: 'Some note for item 1.1'
  },
  {
    _id: 1,
    content: 'Item 2 something',
    note: 'Some note for item 2',
    subItems: [
      {
        _id: 2,
        parent_id: 1,
        content: 'Sub Item 1 something',
        subItems: [{
          _id: 3,
          parent_id: 2,
          content: 'Sub Sub Item 4'
        }]
      }
    ]
  }
];

Using Javascript, how can I navigate/insert into the tree, provided at any point I have the _id of one item in the tree.

For example, some case scenarios:

How do I navigate the tree using only an _id?

Upvotes: 3

Views: 1223

Answers (4)

Maher Fattouh
Maher Fattouh

Reputation: 1860

it's not as easy as "navigating".. the following function will loop through the object and provide things that you can use to achieve what you want.. like name, values, types, number of children and depths.

also look below for specific examples for your case

Main function

you simply call it like this: loopThrough(items)and watch your console for details.

function loopThrough(obj, depth) {
    if (typeof(depth) === "undefined") {
        depth = 0; // depth 0 means the "root" of your object
    } else {
        depth++ // increase depth if exist... depth 1 means a property of an object on depth 0
    }
    for (keyName in obj) {
        let thisObj = obj[keyName] //value of this object
        let type = thisObj.constructor.name // type: Array, Object, String, Number or Function...
        if (type === "Object" || type === "Array") { // to check if this object "have children" to loop through
            let childCount = type === "Object" ? Object.keys(thisObj).length : thisObj.length
            console.group(depth + " (" + type + ") " + keyName + " : " + childCount) // starts a collapsable group called: depth, type and key
            loopThrough(thisObj, depth) //loop through the child object
            console.groupEnd() // closes the group
        } else { // doesn't have children (a String, Number or Function)
            console.log(depth + " (" + type + ") " + keyName + " : " + thisObj) // types: depth, type key and value
        }
    }
}

Example

here's an example targeting _id:3 in this example I added a sibling to the wanted key.

loopThrough(items, "_id", 3)

function loopThrough(obj, wantedKey = "", wantedValue = "", depth) {
    if (typeof(depth) === "undefined") {
        depth = 0;
    } else {
        depth++
    }
    for (keyName in obj) {
        let thisObj = obj[keyName]
        let type = thisObj.constructor.name
        if (type === "Object" || type === "Array") {
            let childCount = type === "Object" ? Object.keys(thisObj).length : thisObj.length
            loopThrough(thisObj, wantedKey, wantedValue, depth)
        }
        if (keyName === wantedKey && thisObj === wantedValue){
          siblings = Object.keys(obj)
          console.log('%c Hello!, I am ' + wantedKey +":"+ wantedValue, 'color: green');
          console.log('%c I have '+ siblings.length + " siblings: " + siblings.toString(), 'color: green');
          console.log("%c adding a new sibling...", 'color: grey')
          obj["new_sibling"] = "new_sibling_value" // add a sibling to _id 3
          siblings = Object.keys(obj)
          console.log('%c now I have '+ siblings.length + " siblings: " + siblings.toString(), 'color: green');
          console.log('%c I am at depth ' + depth, 'color: blue');
          console.log('%c it should be simple to find a way to get my parent _id at depth ' + (depth - 1)  , 'color: blue');ParentID
          console.log(JSON.stringify(items, null, 4));
        }
    }
}
  • for your 2nd request you'll have to tweak the function to store the depth of the wanted key and look for its parent _id at depth - 1 by recalling the function or creating another one

  • for the third request you can count++ the keys and once you find the wantedKey you store the count and loop through again and look for the count - 1 aka previous sibling or count + 1 aka next sibling

as you can see, it's not a simple task, but it's totally possible with some creativity, best of luck.

Upvotes: 0

Nina Scholz
Nina Scholz

Reputation: 386730

You could iterate the array and test if _id property has the wanted value. Then save either the node, the parent or the next item of the array.

For getting the parent node, the actual parent is saved as a closure and returned if the wanted _id is found.

All functions test for subItems as array and if, it performs an iteration over subItems.

function getNode(array, id) {
    var node;
    array.some(function iter(a) {
        if (a._id === id) {
            node = a;
            return true;
        }
        return Array.isArray(a.subItems) && a.subItems.some(iter);
    });
    return node;
}

function getParent(array, id) {
    var parent ;
    array.some(function iter(p) {
        return function (a) {
            if (a._id === id) {
                parent = p;
                return true;
            }
            return Array.isArray(a.subItems) && a.subItems.some(iter(a));
        };
    }(undefined));
    return parent;
}

function getNextNode(array, id) {
    var node;
    array.some(function iter(a, i, aa) {
        if (a._id === id) {
            node = aa[i + 1];
            return true;
        }
        return Array.isArray(a.subItems) && a.subItems.some(iter);
    });
    return node;
}

var items = [{ _id: 0, content: 'Item 1 something', note: 'Some note for item 1' }, { _id: 5, content: 'Item 1.1 something', note: 'Some note for item 1.1' }, { _id: 1, content: 'Item 2 something', note: 'Some note for item 2', subItems: [{ _id: 2, parent_id: 1, content: 'Sub Item 1 something', subItems: [{ _id: 3, parent_id: 2, content: 'Sub Sub Item 4' }] }] }];

console.log(getNode(items, 3));
console.log(getParent(items, 2));
console.log(getNextNode(items, 5));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 2

Karol Selak
Karol Selak

Reputation: 4774

I've developed solution for issues like that. I called it backlooker. My function looks:

var backlooker = function(obj) {
  for (key in obj) {
    if (obj[key]._) {
      break;      
    }
    if (obj[key] instanceof Object) {
      obj[key]._ = obj;
      backlooker(obj[key])
    }
  }
  return obj;
}

You must improve your object first:

items = backlooker(items);

And now you are able to do sth like this:

a = items[2].subItems[0].subItems[0];
c = a._._._._._._._;
c == items; //true

Only one issue: code will not work properly if you already have keys named _ in your object (I think that's very rare situation, but possible).

Upvotes: 0

Quentin
Quentin

Reputation: 944076

Using Javascript, how can I navigate/insert into the tree, provided at any point I have the _id of one item in the tree.

You have to recursively loop over the tree and keep track of where you are and where you have been.

JavaScript references point in one direction. Given nothing but a reference to the object with _id 1, you have no connect to the array it is in at all. (It could even exist in multiple arrays or multiple places in the same array).

In general, you need to search the tree (recursion is your friend) and track the indexes of the members you care about.

Once you know the indexes you are dealing with, you can use splice.

Upvotes: 0

Related Questions