简体   繁体   中英

Most efficient way to remove a list of values from a javascript object by keyname

I need to find the most efficient way to remove values from a arbitrarily nested javascript object based on a list of 'keys-to-remove'. ie

var obj = {a:1, b:2, c:{d:1, e:1}};
var ignoreList = ["a","e"] (could also be ['a', 'c.e'])
removeIgnoredValues(obj, ignoreList) => {b:2, c:{d:1}}.

Now obviously this is easy enough to do if you don't care about efficiency, and my current implementation has been serving me well up till now. But now I'm having to deal with objects that have 6 levels and large arrays of data.

If anyone has a solution or link to one that would be awesome :)

Cheers

EDIT: Current implementation looks like this. It works (and deals with circular references). But is too slow.

/**
 * Returns a sanitised string of an object, removing any functions and unwanted properties.
 * @param {int} obj. The object to be stringified
 * @param {Array[]} ignoreList. A array of object properties that should be removed.
 */
function sanitise(obj, ignoreList){
    if(obj == undefined){
        throw "Can't sanitise an undefined object"
    }
    var entry = JSON.parse(JSON.stringifyOnce(obj));
    for(var i in entry){
        if(entry.hasOwnProperty(i)){
            if(contains(ignoreList, i)){
                delete entry[i];
            } else if(typeof(entry[i]) == "object" && entry[i] != null){
                entry[i] = sanitise(entry[i], ignoreList);
            }
        }
    }
    return entry;
}

JSON.stringifyOnce = function(obj, replacer, indent){
    var printedObjects = [];
    var printedObjectKeys = [];

    function printOnceReplacer(key, value){
        var printedObjIndex = false;
        printedObjects.forEach(function(obj, index){
            if(obj===value){
                printedObjIndex = index;
            }
        });

        if ( key == ''){ //root element
             printedObjects.push(obj);
            printedObjectKeys.push("root");
             return value;
        }

        else if(printedObjIndex+"" != "false" && typeof(value)=="object"){
            if ( printedObjectKeys[printedObjIndex] == "root"){
                return "(pointer to root)";
            }else{
                return "(see " + ((!!value && !!value.constructor) ? value.constructor.name.toLowerCase()  : typeof(value)) + " with key " + printedObjectKeys[printedObjIndex] + ")";
            }
        }else{

            var qualifiedKey = key || "(empty key)";
            printedObjects.push(value);
            printedObjectKeys.push(qualifiedKey);
            if(replacer){
                return replacer(key, value);
            }else{
                return value;
            }
        }
    }
    return JSON.stringify(obj, printOnceReplacer, indent);
};

We can create a look-up table object for the original object, to delete any given key in O(1) time. The implementation will involve you adding custom functions to add/remove from the object.

(function() {
    var lookUpTable = {};
    myObj.prototype.insert = function(key, value) {
        // add key to the myObj
        // insert an Entry for parent of key in lookUpTable
        // lookUpTable = { "a" : [myObj.b, myObj, myObj.c.d.e] }
    }

    myObj.prototype.ignore = function(ignoreList) {
        for( key in ignoreList ) {
             for( parent in lookUpTable[key] )
                  delete parent[key];
             delete lookUpTable [key];
        }
    }
}());

Now you can call the insert function to insert the key-value:

myObj.insert('a.b.c.d', ['p', 'g']);

and call the ignore function to delete the object:

myObj.ignore(['d', 'e']);

Sorry for just giving the incomplete code. But, you should be able to implement the details quiet easily. Hope you get the idea.

For the example given by you:

obj = {a:[{b:1, c:1}, {b:1, c:1}, {b:1, c:1}] 

and you want to ignore all the 'b's. Note that the lookup table entry values were arrays and not just a single value. This is where the power of ignoring multiple entries with the same name, comes in. In this case, the entry for 'b' would be something like this.

lookupTable = {
                  b : [           // The parent objects of 'b'
                          obj['a'][0],
                          obj['a'][1],
                          obj['a'][2]
                       ]
              }

Basically, the lookuptable holds an array of references to all the objects that contain the key 'b' . So, you iterate through each of these parent objects and delete their 'b' entry.

$.each(lookupTable['b'], function( parent ) {
    delete parent['b']; // Deletes 'b' inside of every parent object
});

You populate this lookup table entry while inserting into obj or while obj is loaded for the first time. If obj is hard-coded, you could also generate the lookupTable once and hard-code it. Probably in along with your minify Javascript scripts. Although populating it at run-time is also quiet fine.

Okay, figured out a pretty nice way. You just make an ignore list with roughly the same object structure as the object to be ignored.

function ignore(obj, list){
    for(var i in list){
        var type = Object.prototype.toString.call(list[i]);

        if(type == "[object String]"){
            delete obj[i];
        } 

        else if (type == "[object Object]"){
            ignore(obj[i], list[i])
        } 

        else if (type == "[object Array]"){
            var objList = obj[i];
            var subList = list[i][0];

            for(var n in objList){
                ignore(objList[n], subList)
            }
        }
    }
}

x = {a:1, b:[{c:1, d:1}, {c:1, d:1}, {c:1, d:1}], e:1}
ignoreList = {'e':'e', 'b':[{'c':'c'}]}
ignore(x, ignoreList) => {a:1, b:[{d:1}, {d:1}, {d:1}]}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM