136

What's a good and short way to remove a value from an object at a specific key without mutating the original object?

I'd like to do something like:

let o = {firstname: 'Jane', lastname: 'Doe'};
let o2 = doSomething(o, 'lastname');
console.log(o.lastname); // 'Doe'
console.log(o2.lastname); // undefined

I know there are a lot of immutability libraries for such tasks, but I'd like to get away without a library. But to do this, a requirement would be to have an easy and short way that can be used throughout the code, without abstracting the method away as a utility function.

E.g. for adding a value I do the following:

let o2 = {...o1, age: 31};

This is quite short, easy to remember and doesn't need a utility function.

Is there something like this for removing a value? ES6 is very welcome.

Thank you very much!

amann
  • 5,449
  • 4
  • 38
  • 46
  • "Remove a value without mutating the object" doesn't make any sense. You can't remove from something and keep it intact at the same time. What you're actually doing is making a partial copy. – JJJ Oct 10 '15 at 11:17

10 Answers10

288

Update:

You could remove a property from an object with a tricky Destructuring assignment:

const doSomething = (obj, prop) => {
  let {[prop]: omit, ...res} = obj
  return res
}

Though, if property name you want to remove is static, then you could remove it with a simple one-liner:

let {lastname, ...o2} = o

The easiest way is simply to Or you could clone your object before mutating it:

const doSomething = (obj, prop) => {
  let res = Object.assign({}, obj)
  delete res[prop]
  return res
}

Alternatively you could use omit function from lodash utility library:

let o2 = _.omit(o, 'lastname')

It's available as a part of lodash package, or as a standalone lodash.omit package.

Leonid Beschastny
  • 50,364
  • 10
  • 118
  • 122
  • 3
    Is that really the simplest solution? E.g. for arrays you can do `a2 = a1.filter(el => el.id !== id)` to omit an element within an array by a specific id. Is there no such thing for an object? – amann Oct 10 '15 at 17:51
  • 1
    I agree with @amann. Either there *is* a better solution, or ES6 really missed something about making JS usable in a more functional way. Eg. Ruby has [`keep_if`](http://apidock.com/ruby/Hash/keep_if) – Augustin Riedinger Feb 02 '16 at 11:38
  • 1
    @AugustinRiedinger actually, there is a way. See my update. – Leonid Beschastny Feb 02 '16 at 13:34
  • I was going to suggest the same according to this question: http://stackoverflow.com/questions/35152522/react-transferring-props-except-one/35152952#35152952 – Augustin Riedinger Feb 02 '16 at 13:41
  • 2
    Updated destructuring solution is great. Though my mind is a little blown by it. Why won't `const {[prop], ...rest} = obj` work? Why do you have to assign the extracted property to a new variable (`omit` in this case)? – branweb Apr 02 '17 at 00:09
  • @btwebste How would you otherwise access the dynamic `[prop]` field (`omit`)? `prop` is already taken so you must define a new name. – Filuren Apr 03 '17 at 05:30
  • +1 for the lodash omit, you can also pass it an array of object keys to remove: `const removeStuff = ['this', 'that']; let newObj = _.omit(originalObj, removeStuff);` – David Jul 03 '17 at 22:46
  • Doesn't work when the property I'm trying to remove is an object. – Axel Jul 13 '17 at 21:55
  • Well, it works, but apparently, it will only find the property if you pass in a string.In my case, I was passing a number since that was what I stored as property keys. – Axel Jul 13 '17 at 22:31
  • @Axel not sure what your problem is. It should work fine with numbers as keys: `doSomething({ 1: 'q', 2: 'w' }, 1) // => { '2': 'w' }`. Passing string instead of a number doesnt make any sense since JS treats number and it's stringified version as the same key. – Leonid Beschastny Jul 14 '17 at 08:12
  • This errors out in the browser console on Chrome (`Uncaught SyntaxError: Unexpected token ...`). I tried transpiling it with Babel, but still no joy. Here is a [link to a JSFiddle](https://jsfiddle.net/bengrunfeld/uhqd9ofk/). If I've made a mistake, can you please show me what I've done wrong? TIA. – Ben Aug 24 '17 at 00:45
  • 1
    If property name is not static, still can be one line: `const {[p]: val,...o2} = o;` – Hongbo Miao Nov 13 '17 at 09:03
  • I like this, but I end up with no-unused-vars from eslint. I am prepending _ to ignore unused vars. Anyone have any thought on this? – Antoni4 Sep 25 '18 at 16:38
  • 1
    @Antoni4 you can add "omit" or "omitted" to your `no-unused-vars` rule, it's just regex. – adrianmcli Jan 17 '19 at 19:04
  • 1
    Here's what the eslint rule looks like so no one else has to look it up: ```"no-unused-vars": ["error", {"varsIgnorePattern": "omit" }]``` – kusold Mar 09 '21 at 20:20
56

With ES7 object destructuring:

const myObject = {
  a: 1,
  b: 2,
  c: 3
};
const { a, ...noA } = myObject;
console.log(noA); // => { b: 2, c: 3 }
anothernode
  • 5,100
  • 13
  • 43
  • 62
senbon
  • 766
  • 5
  • 12
  • 1
    what if `a` is stored in a variable `const x = 'a'` ? – FFF Jul 27 '18 at 22:22
  • Changes nothing. a only refers to the first element of the array. It could be like this: `const { a,b, ...noA } = myObject;` `console.log(noA); // => {c: 3 }` – senbon Jul 30 '18 at 12:51
  • 2
    Is there a way to do this if the keys are numbers? – Marc Sloth Eastman Mar 27 '20 at 17:04
  • 2
    Correct me if I'm wrong but this isn't possible if the key to be deleted is dynamic (comes from a variable) like `const {[keyNameOfA], ...noA} = myObject` – PrashanD Feb 12 '21 at 12:06
52

one line solution

const removeKey = (key, {[key]: _, ...rest}) => rest;

Explanations:

This is a generic arrow function to remove a specific key. The first argument is the name of the key to remove, the second is the object from where you want to remove the key. Note that by restructuring it, we generate the curated result, then return it.

Example:

let example = { 
  first:"frefrze",
  second:"gergerge",
  third: "gfgfg"
}

console.log(removeKey('third', example))
/*
Object {
  first: "frefrze",
  second: "gergerge"
}
*/
Billybobbonnet
  • 3,156
  • 4
  • 23
  • 49
punksta
  • 2,738
  • 3
  • 23
  • 41
6

To add some spice bringing in Performance. Check this thread bellow

https://github.com/googleapis/google-api-nodejs-client/issues/375

The use of the delete operator has performance negative effects for the V8 hidden classes pattern. In general it's recommended do not use it.

Alternatively, to remove object own enumerable properties, we could create a new object copy without those properties (example using lodash):

_.omit(o, 'prop', 'prop2')

Or even define the property value to null or undefined (which is implicitly ignored when serializing to JSON):

o.prop = undefined

You can use too the destructing way

const {remov1, remov2, ...new} = old;
old = new;

And a more practical exmple:

this._volumes[this._minCandle] = undefined;
{ 
     const {[this._minCandle]: remove, ...rest} = this._volumes;
     this._volumes = rest; 
}

As you can see you can use [somePropsVarForDynamicName]: scopeVarName syntax for dynamic names. And you can put all in brackets (new block) so the rest will be garbage collected after it.

Here a test: enter image description here

exec:

enter image description here

Or we can go with some function like

function deleteProps(obj, props) {
    if (!Array.isArray(props)) props = [props];
    return Object.keys(obj).reduce((newObj, prop) => {
        if (!props.includes(prop)) {
            newObj[prop] = obj[prop];
        }
        return newObj;
    }, {});
}

for typescript

function deleteProps(obj: Object, props: string[]) {
    if (!Array.isArray(props)) props = [props];
    return Object.keys(obj).reduce((newObj, prop) => {
        if (!props.includes(prop)) {
            newObj[prop] = obj[prop];
        }
        return newObj;
    }, {});
}

Usage:

let a = {propH: 'hi', propB: 'bye', propO: 'ok'};

a = deleteProps(a, 'propB'); 

// or 

a = deleteProps(a, ['propB', 'propO']);

This way a new object is created. And the fast property of the object is kept. Which can be important or matter. If the mapping and the object will be accessed many many times.

Also associating undefined can be a good way to go with. When you can afford it. And for the keys you can too check the value. For instance to get all the active keys you do something like:

const allActiveKeys = Object.keys(myObj).filter(k => myObj[k] !== undefined);
//or
const allActiveKeys = Object.keys(myObj).filter(k => myObj[k]); // if any false evaluated value is to be stripped.

Undefined is not suited though for big list. Or development over time with many props to come in. As the memory usage will keep growing and will never get cleaned. So it depend on the usage. And just creating a new object seem to be the good way.

Then the Premature optimization is the root of all evil will kick in. So you need to be aware of the trade off. And what is needed and what's not.

Note about _.omit() from lodash

It's removed from version 5. You can't find it in the repo. And here an issue that talk about it.

https://github.com/lodash/lodash/issues/2930

v8

You can check this which is a good reading https://v8.dev/blog/fast-properties

Mohamed Allal
  • 17,920
  • 5
  • 94
  • 97
  • hmm 2 years later and still no sign of lodash 5.0 being released!?! – Andy Oct 18 '21 at 08:25
  • True. 4 seems to go for long. The answer needs an update. on that part. If you go to there master. And search for the files. You'll not find omit. On the other hand if you take the latest release and download the zip. You'll find it include omit. so omit is still there. I didn't check how they are building and setting up there modules. I'll update the answer a bit. Still the link above is relevant for the answer. – Mohamed Allal Oct 19 '21 at 00:41
5

As suggested in the comments above if you want to extend this to remove more than one item from your object I like to use filter. and reduce

eg

    const o = {
      "firstname": "Jane",
      "lastname": "Doe",
      "middlename": "Kate",
      "age": 23,
      "_id": "599ad9f8ebe5183011f70835",
      "index": 0,
      "guid": "1dbb6a4e-f82d-4e32-bb4c-15ed783c70ca",
      "isActive": true,
      "balance": "$1,510.89",
      "picture": "http://placehold.it/32x32",
      "eyeColor": "green",
      "registered": "2014-08-17T09:21:18 -10:00",
      "tags": [
        "consequat",
        "ut",
        "qui",
        "nulla",
        "do",
        "sunt",
        "anim"
      ]
    };

    const removeItems = ['balance', 'picture', 'tags']
    console.log(formatObj(o, removeItems))

    function formatObj(obj, removeItems) {
      return {
        ...Object.keys(obj)
          .filter(item => !isInArray(item, removeItems))
          .reduce((newObj, item) => {
            return {
              ...newObj, [item]: obj[item]
            }
          }, {})
      }
    }

    function isInArray(value, array) {
      return array.indexOf(value) > -1;
    }
ak85
  • 4,154
  • 18
  • 68
  • 113
  • 1
    Why don't you apply the filter condition in the reduce, so you can save yourself one loop? – Ivo Sabev Apr 19 '18 at 08:17
  • thanks for the tip @IvoSabev I have a few functions in my code where I do filter then reduce but will consider your suggestion going forward to save the loop. – ak85 Apr 19 '18 at 20:09
3
export function deleteKeyFromObject(obj, key) {
  return Object.fromEntries(Object.entries(obj).filter(el => el[0] !== key))
}
  • 4
    Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Mark Rotteveel Nov 05 '21 at 16:11
2

My issue with the accepted answer, from an ESLint rule standard, if you try to destructure:

    const { notNeeded, alsoNotNeeded, ...rest } = { ...ogObject };

the 2 new variables, notNeeded and alsoNotNeeded may throw a warning or error depending on your setup since they are now unused. So why create new vars if unused?

I think you need to use the delete function truly.

Phil Lucks
  • 2,704
  • 6
  • 31
  • 57
  • 4
    The `no-unused-vars` rule actually has an option `ignoreRestSiblings` now to cover this use case: https://eslint.org/docs/rules/no-unused-vars#ignorerestsiblings – amann Jul 31 '20 at 14:26
0

with lodash cloneDeep and delete

(note: lodash clone can be used instead for shallow objects)

const obj = {a: 1, b: 2, c: 3}
const unwantedKey = 'a'

const _ = require('lodash')
const objCopy = _.cloneDeep(obj)
delete objCopy[unwantedKey]
// objCopy = {b: 2, c: 3}
FFF
  • 741
  • 1
  • 8
  • 19
0

For my code I wanted a short version for the return value of map() but the multiline/mutli operations solutions were "ugly". The key feature is the old void(0) which resolve to undefined.

let o2 = {...o, age: 31, lastname: void(0)};

The property stays in the object:

console.log(o2) // {firstname: "Jane", lastname: undefined, age: 31}

but the transmit framework kills it for me (b.c. stringify):

console.log(JSON.stringify(o2)) // {"firstname":"Jane","age":31}
Jonny
  • 335
  • 4
  • 15
0

I wrote big function about issue for me. The function clear all values of props (not itself, only value), arrays etc. as multidimensional.

NOTE: The function clear elements in arrays and arrays become an empty array. Maybe this case can be added to function as optional.

https://gist.github.com/semihkeskindev/d979b169e4ee157503a76b06573ae868

function clearAllValues(data, byTypeOf = false) {

    let clearValuesTypeOf = {
        boolean: false,
        number: 0,
        string: '',
    }

    // clears array if data is array
    if (Array.isArray(data)) {
        data = [];
    } else if (typeof data === 'object' && data !== null) {
        // loops object if data is object
        Object.keys(data).forEach((key, index) => {
            // clears array if property value is array
            if (Array.isArray(data[key])) {
                data[key] = [];
            } else if (typeof data[key] === 'object' && data !== null) {
                data[key] = this.clearAllValues(data[key], byTypeOf);
            } else {
                // clears value by typeof value if second parameter is true
                if (byTypeOf) {
                    data[key] = clearValuesTypeOf[typeof data[key]];
                } else {
                    // value changes as null if second parameter is false
                    data[key] = null;
                }
            }
        });
    } else {
        if (byTypeOf) {
            data = clearValuesTypeOf[typeof data];
        } else {
            data = null;
        }
    }

    return data;
}

Here is an example that clear all values without delete props

let object = {
    name: 'Semih',
    lastname: 'Keskin',
    brothers: [
        {
            name: 'Melih Kayra',
            age: 9,
        }
    ],
    sisters: [],
    hobbies: {
        cycling: true,
        listeningMusic: true,
        running: false,
    }
}

console.log(object);
// output before changed: {"name":"Semih","lastname":"Keskin","brothers":[{"name":"Melih Kayra","age":9}],"sisters":[],"hobbies":{"cycling":true,"listeningMusic":true,"running":false}}

let clearObject = clearAllValues(object);

console.log(clearObject);
// output after changed: {"name":null,"lastname":null,"brothers":[],"sisters":[],"hobbies":{"cycling":null,"listeningMusic":null,"running":null}}

let clearObject2 = clearAllValues(object);

console.log(clearObject2);
// output after changed by typeof: {"name":"","lastname":"","brothers":[],"sisters":[],"hobbies":{"cycling":false,"listeningMusic":false,"running":false}}
Semih Keskin
  • 19
  • 1
  • 1