12

I am curious about an improved way to dynamically delete properties from a javascript object based on wildcards. Firstly, suppose I have the following object:

object =
{
    checkbox_description_1 : 'Chatoyant',
    checkbox_description_2 : 'Desultory',
    random_property : 'Firefly is a great program',
    checkbox_mood_1 : 'Efflorescent',
    checkbox_description_3 : 'Ephemeral'
}

Task

Now, the end result is to have removed all properties under the guise of 'checkbox_description' and leave the rest of the object intact as shown:

object =
{
    random_property : 'Firefly is a great program',
    checkbox_mood_1 : 'Efflorescent',
}

My solution

At present my solution involves jquery and the following code:

var strKeyToDelete = 'checkbox_description'

/* Start looping through the object */
$.each(object, function(strKey, strValue) {

    /* Check if the key starts with the wildcard key to delete */
    if(this.match("^"+strKey) == strKeyToDelete) {

        /* Kill... */
        delete object[strKey];
    };
});

Issue

Something about this seems very inelegant to me and if the object were to be of reasonable size very process intensive. Is there a better way of performing this operation?

Ian
  • 50,146
  • 13
  • 101
  • 111
Symbal
  • 360
  • 1
  • 3
  • 12

4 Answers4

12

This is the bare minimum required:

function deleteFromObject(keyPart, obj){
    for (var k in obj){          // Loop through the object
        if(~k.indexOf(keyPart)){ // If the current key contains the string we're looking for
            delete obj[k];       // Delete obj[key];
        }
    }
}

var myObject = {
    checkbox_description_1 : 'Chatoyant',
    checkbox_description_2 : 'Desultory',
    random_property : 'Firefly is a great program',
    checkbox_mood_1 : 'Efflorescent',
    checkbox_description_3 : 'Ephemeral'
};
deleteFromObject('checkbox_description', myObject);
console.log(myObject);
// myObject is now: {random_property: "Firefly is a great program", checkbox_mood_1: "Efflorescent"};

So that's pretty close to the jQuery function you have.
(Though a little faster, considering it doesn't use jQuery, and indexOf instead of match)

So, what's with the ~ before indexOf?

indexOf returns a integer value: -1 if the string is not found, and a index, starting from 0, if it is found. (So always a positive integer if found)
~ is a bitwise NOT, that inverts this output. As it happens to be, the inverted output of indexOf is just what we need to indicate "found" or "not found".

~-1 becomes 0, a false-ish value.
~x, where x is 0 or postitive, becomes -(x+1), a true-ish value.

This way, ~string.indexOf('needle') acts like string.contains('needle'), a function that we don't have in JavaScript.

Additionally, you could add a double boolean not (!!) in front of the ~, to convert the true-ish or false-ish output to a real true / false, but that's not necessary in JavaScript.
Functionally, ~string.indexOf('needle') and !!~string.indexOf('needle') are equal.


In case you specifically need the key to begin with the needle, replace the:

~k.indexOf(keyPart)

With:

k.indexOf(keyPart) === 0
Cerbrus
  • 70,800
  • 18
  • 132
  • 147
  • 1
    Might be worth explaining what the `~` does in there - its not a commonly seen operator in js. – Jamiec Dec 14 '12 at 08:57
  • What if keypart is at index 0? – Jonathan de M. Dec 14 '12 at 08:59
  • @JonathandeM.: That's what the `~` is for. – Cerbrus Dec 14 '12 at 09:06
  • @Cerbrus. Nice +1! (Shouldn't it be "where x is **zero** or positive") – Jamiec Dec 14 '12 at 09:06
  • @Cerbrus That's a nice solution, no reliance on jQuery is a bonus. From your answer, I learnt a little more javascript, thank you. – Symbal Dec 14 '12 at 09:10
  • 1
    Didn't the OP want the key to *begin with* the wildcard string, instead of to *contain* it? – Bergi Dec 14 '12 at 11:27
  • @Cerbrus Awesome tip "with the ~ before indexOf?" while I wouldn't personally use it 'every day' (as the effort to read it doesn't justify the syntax) I love finding these 'well I didn't know that but it's real cool' moments. – Minty Feb 09 '21 at 18:23
8
    var myObject = {
        checkbox_description_1 : 'Chatoyant',
        checkbox_description_2 : 'Desultory',
        random_property : 'Firefly is a great program',
        checkbox_mood_1 : 'Efflorescent',
        checkbox_description_3 : 'Ephemeral'
   };

const removeProperty = dyProps => ({ [dyProps]: _, ...rest }) => rest;

const remove_random_property = removeProperty('random_property');
console.log(remove_random_property(myObject));
Dheeraj kumar Rao
  • 8,132
  • 3
  • 22
  • 24
3

You can use How to check if a string "StartsWith" another string?:

function deleteFromObject(keyToDelete, obj) {
    var l = keyToDelete.length;
    for (var key in obj)
        if (key.substr(0, l) == keyToDelete) // key begins with the keyToDelete
            delete obj[key];
}
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I have to wonder if `indexOf` and `substr` use the same algorithm behind the scenes, except `indexOf` searches at every index, not just the one specified with `substr`. Which would make this much more efficient for non-matches – Ian Dec 14 '12 at 15:41
  • Yes, that's the point of using substr :-) I don't think you can compare the algorithms, as `substr` does not search. Of course, an even better implementation would not extract the whole substr, but compare character for character so it can break early. – Bergi Dec 14 '12 at 15:51
  • Right, I guess I meant that I wonder if `indexOf` uses `substr` (or an algorithm just like it) over and over again, from index `0` to `length-1` (and maybe short-circuits after the certain point where it isn't necessary to do the search anymore). Like hypothetically if there were a second parameter for `indexOf` that said at what index to stop searching...and if you passed it `1`, it would be "identical" to the use of `substr` in this example of "starting with". I'm not sure if comparing character for character would necessarily be better in terms of work to do, but makes more sense at least – Ian Dec 14 '12 at 21:53
  • `indexOf` *has* a second parameter that says where to *start* searching. And yes, from there it is compared character by character internally; a soon as it does not match it tries from next index. – Bergi Dec 15 '12 at 18:46
  • I forgot about the actual second parameter! Well then I meant a third parameter! Anyways, it was hypothetical. I think you get it. But nevermind :) – Ian Dec 15 '12 at 20:57
1

If you're looking for a solution that doesn't mutate the original object, you can try something like this

const omit = (source = {}, omitKeys = []) => (
  Object.keys(source).reduce((output, key) => (
    omitKeys.includes(key) ? output : {...output, [key]: source[key]}
  ), {})
)

Tests

const original = {a:1, b:2, c:3, d:4, e:5}
console.log('original: ', JSON.stringify(original));

const modified = omit(original, ['b', 'd'])
console.log('modified: ', JSON.stringify(modified));
console.log('original: ', JSON.stringify(original));

// Will log: 
// original: {"a":1,"b":2,"c":3,"d":4,"e":5}
// modified: {"a":1,"c":3,"e":5}
// original: {"a":1,"b":2,"c":3,"d":4,"e":5}

This will create a new object and spread all properties of the source object into it, except those included in the exception list (omitKeys).