Remova o valor do objeto sem mutação


96

Qual é uma maneira boa e curta de remover um valor de um objeto em uma chave específica sem alterar o objeto original?

Eu gostaria de fazer algo como:

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

Sei que existem muitas bibliotecas de imutabilidade para essas tarefas, mas gostaria de sair sem uma biblioteca. Mas, para fazer isso, um requisito seria ter um caminho fácil e curto que possa ser usado em todo o código, sem abstrair o método como uma função de utilidade.

Por exemplo, para adicionar um valor, faço o seguinte:

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

É muito curto, fácil de lembrar e não precisa de uma função de utilidade.

Existe algo assim para remover um valor? ES6 é muito bem-vindo.

Muito obrigado!


"Remover um valor sem alterar o objeto" não faz sentido. Você não pode remover algo e mantê-lo intacto ao mesmo tempo. O que você está realmente fazendo é uma cópia parcial.
JJJ de

Respostas:


215

Atualizar:

Você pode remover uma propriedade de um objeto com uma atribuição complicada de Destructuring :

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

Porém, se o nome da propriedade que você deseja remover for estático, você pode removê-lo com uma linha simples:

let {lastname, ...o2} = o

A maneira mais fácil é simplesmente clonar seu objeto antes de transformá-lo:

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

Alternativamente, você pode usar a omitfunção da lodashbiblioteca de utilitários :

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

Está disponível como parte do pacote lodash ou como pacote lodash.omit independente .


3
Essa é realmente a solução mais simples? Por exemplo, para matrizes, você pode a2 = a1.filter(el => el.id !== id)omitir um elemento dentro de uma matriz por um id específico. Não existe tal coisa para um objeto?
amann

1
Eu concordo com @amann. Ou não é uma solução melhor, ou ES6 realmente perdido alguma coisa sobre fazer JS utilizável de uma forma mais funcional. Por exemplo. Ruby temkeep_if
Augustin Riedinger

1
@AugustinRiedinger na verdade, existe uma maneira. Veja minha atualização.
Leonid Beschastny

Eu ia sugerir o mesmo de acordo com esta pergunta: stackoverflow.com/questions/35152522/…
Augustin Riedinger

1
A solução de desestruturação atualizada é ótima. Embora minha mente esteja um pouco confusa com isso. Por que não const {[prop], ...rest} = objfunciona? Por que você tem que atribuir a propriedade extraída a uma nova variável ( omitneste caso)?
branweb

28

Com a desestruturação de objetos ES7:

const myObject = {
  a: 1,
  b: 2,
  c: 3
};
const { a, ...noA } = myObject;
console.log(noA); // => { b: 2, c: 3 }

1
e se aestiver armazenado em uma variável const x = 'a'?
FFF

Não muda nada. a refere-se apenas ao primeiro elemento da matriz. Pode ser assim: const { a,b, ...noA } = myObject; console.log(noA); // => {c: 3 }
senbon

1
esta é a solução mais elegante e simples
Joe

1
Existe uma maneira de fazer isso se as chaves forem números?
Marc Sloth Eastman

22

solução de uma linha

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

4

Conforme sugerido nos comentários acima, se você quiser estender isso para remover mais de um item de seu objectgosto de usar filter. ereduce

por exemplo

    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;
    }


1
Por que você não aplica a condição de filtro na redução, para que possa economizar um loop?
Ivo Sabev

obrigado pela dica @IvoSabev. Tenho algumas funções em meu código onde filtro e depois reduzo, mas considerarei sua sugestão de salvar o loop.
ak85 de

3

Para adicionar algum tempero trazendo desempenho. Verifique este tópico abaixo

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

O uso do operador delete tem efeitos negativos de desempenho para o padrão de classes ocultas V8. Em geral, é recomendável não usá-lo.

Como alternativa, para remover as propriedades enumeráveis ​​do próprio objeto, poderíamos criar uma nova cópia do objeto sem essas propriedades (exemplo usando lodash):

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

Ou até mesmo defina o valor da propriedade como nulo ou indefinido (que é implicitamente ignorado ao serializar para JSON):

o.prop = indefinido

Você também pode usar a forma destrutiva

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

E um exemplo mais prático:

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

Como você pode ver, você pode usar a [somePropsVarForDynamicName]: scopeVarNamesintaxe para nomes dinâmicos. E você pode colocar tudo entre colchetes (novo bloco) para que o resto seja coletado como lixo depois disso.

Aqui está um teste: insira a descrição da imagem aqui

exec:

insira a descrição da imagem aqui

Ou podemos ir com alguma função como

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;
    }, {});
}

para texto datilografado

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;
    }, {});
}

Uso:

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

a = deleteProps(a, 'propB'); 

// or 

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

Desta forma, um novo objeto é criado. E a propriedade rápida do objeto é mantida. O que pode ser importante ou importante. Se o mapeamento e o objeto serão acessados ​​muitas vezes.

Associar-se também undefinedpode ser uma boa maneira de prosseguir. Quando você puder pagar. E para as chaves, você também pode verificar o valor. Por exemplo, para obter todas as chaves ativas, você faz algo como:

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 não é adequado para uma lista grande. Ou desenvolvimento ao longo do tempo com muitos adereços surgindo. Como o uso de memória continuará crescendo e nunca será limpo. Portanto, depende do uso. E apenas criar um novo objeto parece ser o bom caminho.

Em seguida, o Premature optimization is the root of all evilserá ativado. Portanto, você precisa estar ciente da troca. E o que é necessário e o que não é.

Nota sobre _.omit () de lodash

Ele foi removido da versão 5. Você não pode encontrá-lo no repo. E aqui está uma questão que falamos sobre isso.

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

v8

Você pode verificar isso, o que é uma boa leitura https://v8.dev/blog/fast-properties


0

com lodash cloneDeep e delete

(nota: clone lodash pode ser usado em vez de objetos rasos)

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}

0

Para meu código, eu queria uma versão curta para o valor de retorno de map (), mas as soluções de operações multiline / mutli eram "feias". A principal característica é o antigo void(0)que resolve undefined.

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

A propriedade permanece no objeto:

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

mas a estrutura de transmissão mata para mim (bc stringify):

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

0

Meu problema com a resposta aceita, de um padrão de regra ESLint, se você tentar desestruturar:

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

as 2 novas variáveis notNeedede alsoNotNeededpodem lançar um aviso ou erro dependendo de sua configuração, uma vez que agora não são utilizadas. Então, por que criar novos vars se não forem usados?

Acho que você precisa deleterealmente usar a função.


3
A no-unused-varsregra, na verdade, tem uma opção ignoreRestSiblingsagora para cobrir este caso de uso: eslint.org/docs/rules/no-unused-vars#ignorerestsiblings
amann
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.