1

I have something similar to a tree structure in my javascript program and I want to remove a child from it, I used a recursive approach to traverse the tree but I am quite stuck in deleting a node from the tree.

In contrast, I will get an ID as a parameter and I should check it against the child.metadata.id and delete the child if it maches with the ID.

Note : I have certain type of child nodes in my graph where it divides it path based on its type 'true' or 'false' that is if the type of the child node in the metadata is if/empty.

Here is the sample tree structure.

 var graph =
{
    "metadata": {"id": "sms_in", "type": "api", "optionsDivId": "div_sms_in", "options": {}},
    "data": {
        "true": {
            "isEmpty1": {
                "metadata": {
                    "id": "isEmpty1",
                    "type": "empty",
                    "optionsDivId": "div_isEmpty1",
                    "options": {}
                },
                "data": {
                    "true": {
                        "sms1": {
                            "metadata": {
                                "id": "sms1",
                                "type": "api",
                                "optionsDivId": "div_sms1",
                                "options": {}
                            }, "data": {"true": {}, "false": false}
                        }
                    },
                    "false": {
                        "dbInsert1": {
                            "metadata": {
                                "id": "dbInsert1",
                                "type": "dbInsert",
                                "optionsDivId": "div_dbInsert1",
                                "options": {}
                            },
                            "data": {
                                "true": {
                                    "sms2": {
                                        "metadata": {
                                            "id": "sms2",
                                            "type": "api",
                                            "optionsDivId": "div_sms2",
                                            "options": {}
                                        }, "data": {"true": {}, "false": false}
                                    }
                                }, "false": false
                            }
                        }
                    }
                }
            }
        }, "false": false
    }
};

and this is my traverse function

var traverse = function (current) {



    if( current == 'undefined' ) return ;

    var currentChildId = current['metadata']['id'];
    var currentChildType = current['metadata']['type'];



    console.log('visiting : ', currentChildId);


    if (currentChildType == 'if' || currentChildType == 'empty') {

        for(var childKeyType in current['data']){

            for( var childKey in current['data'][childKeyType]){

                var child = current['data'][childKeyType][childKey];

                traverse(child);

            }

        }


    } else {

        for (var childKey in current['data']['true']) {

            var child = current['data']['true'][childKey];

            traverse(child);

        }

    }


};

Can someone help me to complete the delete function ?

function popChild(current, childId){
    if( current == 'undefined' ) return ;

    var currentChildId = current['metadata']['id'];
    var currentChildType = current['metadata']['type'];

    if(currentChildId == childId){
        delete current;
        return;
    }


    console.log('visiting : ', currentChildId);


    if (currentChildType == 'if' || currentChildType == 'empty') {

        for(var childKeyType in current['data']){

            for( var childKey in current['data'][childKeyType]){

                var child = current['data'][childKeyType][childKey];

                popChild(child, childId, );

            }

        }


    } else {

        for (var childKey in current['data']['true']) {

            var child = current['data']['true'][childKey];

            popChild(child, childId);

        }

    }
}
1
  • I don't have the solution for you but I'm pretty sure you are running into a JavaScript fundamental concept that when you pass an object to a function, if you reassign the Object itself in the function, the original won't be changed, but if you reassign one of the Object's properties, that will affect the original Object. So you cannot delete current; and expect it to reflect on the original object. Instead I would maybe try to create a recursive function that simply generates a path to the child you want to delete and then apply that string to delete the child from the top of the hierarchy. Commented Nov 30, 2015 at 2:27

3 Answers 3

4

Consider using JSON.stringify as a way to iterate through your object, using the replacer argument:

function remove_ids(object, id) {
  return JSON.parse(JSON.stringify(object, function(key, value) {
    if (typeof value !== 'object' || typeof value.metadata !== 'object' ||
        value.metadata.id !== id) return value;
  }));
}
Sign up to request clarification or add additional context in comments.

2 Comments

how do I call this function on my graph object?
remove_ids(graph, 'isEmpty1')
0

Since this has been resurrected...

Vincent points out that battle-tested libraries are often the best way to solve such problems. While I certainly must agree (and am in fact one of the principal authors of Ramda), there is another approach of maintaining one's own collection of snippets to reuse across projects.

Although I've never really considered including it in Ramda, I have handy a recursive filter function that makes writing this pretty simple:

const filterDeep = (pred) => (obj) => 
  Object (obj) === obj
    ? Object .fromEntries (
        Object .entries (obj) 
          .flatMap (([k, v]) => pred (v) ? [[k, filterDeep (pred) (v)]] : [])
      )
    : obj

const removeItem = (targetId, graph) => 
  filterDeep (({metadata: {id} = {}}) => id !== targetId) (graph)

const graph = {metadata: {id: "sms_in", type: "api", optionsDivId: "div_sms_in", options: {}}, data: {true: {isEmpty1: {metadata: {id: "isEmpty1", type: "empty", optionsDivId: "div_isEmpty1", options: {}}, data: {true: {sms1: {metadata: {id: "sms1", type: "api", optionsDivId: "div_sms1", options: {}}, data: {true: {}, false: !1}}}, false: {dbInsert1: {metadata: {id: "dbInsert1", type: "dbInsert", optionsDivId: "div_dbInsert1", options: {}}, data: {true: {sms2: {metadata: {id: "sms2", type: "api", optionsDivId: "div_sms2", options: {}}, data: {true: {}, false: !1}}}, false: !1}}}}}}, false: !1}}

console .log (removeItem ('dbInsert1', graph))
console .log (removeItem ('sms2', graph))
.as-console-wrapper {max-height: 100% !important; top: 0}

Here, we write a very simple removeItem function, depending on our already tested filterDeep. This is pretty powerful. Functions like this are often not as hardened as those in popular libraries. But they are written directly for our own needs, and can be designed precisely how we want them to be.

For instance, there is something odd about using filter with a predicate that says the opposite of what we want, though. It might make sense to write a rejectDeep that would let our predicate test for a match rather than a mismatch. It turns out to be trivial:

const rejectDeep = (pred) => 
  filterDeep (x => ! (pred (x)))

const removeItem = (target, obj) => 
  rejectDeep (({metadata: {id} = {}}) => id === target) (obj)

Now we can then add rejectDeep to our personal library.


There is one part of filterDeep that might be confusing. We use flatMap here as a one-pass filter and map combination. It would be perfectly reasonable, but (possibly) slightly less efficient, to separate it into two steps:

const filterDeep = (pred) => (obj) => 
  Object (obj) === obj
    ? Object .fromEntries (
        Object .entries (obj) 
          .filter (([k, v]) => pred (v)) 
          .map (([k, v]) => [k, filterDeep (pred) (v)])
      )
    : obj

Comments

0

I'd try not to reinvent the wheel here. We use object-scan for most of our data processing like this. It's powerful once you wrap your head around it and makes the code a lot more maintainable. Here is how you could solve your problem

// const objectScan = require('object-scan');

const rm = (obj, id) => objectScan(['++.metadata.id'], {
  abort: true,
  rtn: 'bool',
  filterFn: ({ value, parents, key }) => {
    if (value === id) {
      delete parents[2][key[key.length - 3]];
      return true;
    }
    return false;
  }
})(obj);

const graph = { metadata: { id: 'sms_in', type: 'api', optionsDivId: 'div_sms_in', options: {} }, data: { true: { isEmpty1: { metadata: { id: 'isEmpty1', type: 'empty', optionsDivId: 'div_isEmpty1', options: {} }, data: { true: { sms1: { metadata: { id: 'sms1', type: 'api', optionsDivId: 'div_sms1', options: {} }, data: { true: {}, false: false } } }, false: { dbInsert1: { metadata: { id: 'dbInsert1', type: 'dbInsert', optionsDivId: 'div_dbInsert1', options: {} }, data: { true: { sms2: { metadata: { id: 'sms2', type: 'api', optionsDivId: 'div_sms2', options: {} }, data: { true: {}, false: false } } }, false: false } } } } } }, false: false } };

console.log(rm(graph, 'isEmpty1'));
// => true

console.log(graph);
/* =>
  { metadata:
    { id: 'sms_in',
      type: 'api',
      optionsDivId: 'div_sms_in',
      options: {} },
   data: { true: {}, false: false } }
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/[email protected]"></script>

Disclaimer: I'm the author of object-scan

Note that since the code mutates the existing structure, the delete can not work for the root node.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.