3

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:

  • I am at _id 3 and want to insert another sibling to _id 3
  • I am at _id 2 and want to go up to _id 1 - how do I get _id 1?
  • I am at _id 5 and want to go to _id 1

How do I navigate the tree using only an _id?

Karol Selak
  • 4,248
  • 6
  • 35
  • 65
Hirvesh
  • 7,636
  • 15
  • 59
  • 72
  • my bad, corrected the title – Hirvesh Feb 06 '17 at 08:29
  • do you mean you don't know the "depth/hierarchy" of the object and you wanna add a sibling to it? – Maher Fattouh Feb 06 '17 at 08:32
  • exactly, at any point in time, I will be within an "item" from the array of "items". I want to be able to get the item _id for the one before it or after it. – Hirvesh Feb 06 '17 at 08:34
  • It's possible, but tricky. are you aware of possible duplication, the answer that I will propose will use the object name. so if you have 2 name you could get unexpected results, are you ok with this? – Maher Fattouh Feb 06 '17 at 08:37
  • the _id will be unique for each item, do duplication won't be an isssue – Hirvesh Feb 06 '17 at 08:39
  • 1
    look at Karol Selak solution, it's what I was thinking of. you'll need to loop through your object and compare key names and when found handle or return the result. also have a look my answer here http://stackoverflow.com/questions/41965517/how-to-reverse-the-hierarchy-of-a-json-object it deals with depth and hierarchy and can give you a clue: – Maher Fattouh Feb 06 '17 at 08:46
  • I think that's quite frequent problem, it would be good to vote it up. – Karol Selak Feb 06 '17 at 08:52

4 Answers4

2

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; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

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.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
0

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).

Karol Selak
  • 4,248
  • 6
  • 35
  • 65
0

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.

Maher Fattouh
  • 1,742
  • 16
  • 24