Em Javascript, como adicionar condicionalmente um membro a um objeto?


387

Eu gostaria de criar um objeto com um membro adicionado condicionalmente. A abordagem simples é:

var a = {};
if (someCondition)
    a.b = 5;

Agora, eu gostaria de escrever um código mais idiomático. Eu estou tentando:

a = {
    b: (someCondition? 5 : undefined)
};

Mas agora, bé um membro acujo valor é undefined. Este não é o resultado desejado.

Existe uma solução útil?

Atualizar

Eu procuro uma solução que possa lidar com o caso geral com vários membros.

a = {
  b: (conditionB? 5 : undefined),
  c: (conditionC? 5 : undefined),
  d: (conditionD? 5 : undefined),
  e: (conditionE? 5 : undefined),
  f: (conditionF? 5 : undefined),
  g: (conditionG? 5 : undefined),
 };

23
Sim existe; o primeiro bit de código.
— Paolo Bergantino

6
Não tenho certeza que há uma coisa como JavaScript idiomática ...
— Michael Berkowski

Isso realmente importa? Se você nunca definiu a.b, a recuperação a.bretornaria de undefinedqualquer maneira.
— Teemu

6
@Teemu: Pode ser importante quando o inoperador é usado.

11
Por enquanto, não há como ter propriedades condicionais em objetos literais, mas gostaria que elas fossem adicionadas no ES7. Isso pode ser muito útil, especialmente em programação no servidor!
— Ali

Respostas:


118

No Javascript puro, não consigo pensar em nada mais idiomático do que o seu primeiro trecho de código.

Se, no entanto, o uso da biblioteca jQuery não estiver fora de questão, $ .extend () deverá atender aos seus requisitos, pois, como diz a documentação:

Propriedades indefinidas não são copiadas.

Portanto, você pode escrever:

var a = $.extend({}, {
    b: conditionB ? 5 : undefined,
    c: conditionC ? 5 : undefined,
    // and so on...
});

E obtenha os resultados esperados (se conditionBhouver false, então bnão existirão a).


21
Existe um método muito melhor que não requer jQuery. Dê uma olhada na resposta de Jamie Hill.
— Andrew Rasmussen

13
@ Andrew, essa resposta requer o ES6, que não existia no momento em que escrevi o meu.
— Frédéric Hamidi

null funciona da mesma maneira? ou precisa ser indefinido?
— Aous1000

Esta é realmente uma resposta errada, porque usa jQuery e essa condição ternária não remove uma propriedade de um objeto, apenas define uma propriedade como indefinida. Veja @lagistos responder para a maneira correta de fazer isso,
— Alexander Kim

Sim, confira a resposta de Jamie abaixo: stackoverflow.com/a/40560953/10222449
— MadMac

872

Acho que o @InspiredJW fez isso com o ES5 e, como o @trincot apontou, usar o es6 é uma abordagem melhor. Mas podemos adicionar um pouco mais de açúcar usando o operador spread e a avaliação lógica E de curto-circuito:

const a = {
   ...(someCondition && {b: 5})
}

2
Não tenho tanta certeza de que isso esteja correto, afirma a propostaNull/Undefined Are Ignored , ela não diz que falseé ignorada. Os transpilers podem permitir isso até o momento, mas é compatível? O seguinte deve ser, {...someCondition ? {b: 5} : null}mas não é tão compacto.
— Benjamin Dobell 28/02

24
Perguntei se isso era válido para as pessoas que fizeram a proposta de divulgação e disseram que está tudo bem. github.com/tc39/proposal-object-rest-spread/issues/45 , cc @BenjaminDobell
— #

73
O operador de propagação @AlanH é uma abreviação Object.assigne tem precedência menor do que o operador &&. Ele ignora o valor sem propriedade (booleano, nulo, indefinido, número) e adiciona todas as propriedades do objeto após o ...local. lembre-se de que o &&operador retornará o valor correto se verdadeiro, ou falso caso contrário. por isso, se someConditionfor verdade, {b : 5}será passado para o ...operador, resultando em adição a propriedade bpara acom o valor 5. é someConditionfalse, falseserá passado para o ...operador. resultando em nada adicionado. é inteligente. Eu amo isso.
— Félix Brunet

11
Ótima resposta, mas colocar a condição e o objeto resultante entre parênteses melhorará bastante a legibilidade deste exemplo. Nem todo mundo se lembra da precedência do operador JS de cor.
— Neurotransmissor

6
A única outra questão é que você não pode usar isso para booleanos falsos.
— precisa saber é o seguinte

92

Com o EcmaScript2015, você pode usar Object.assign:

Object.assign(a, conditionB ? { b: 1 } : null,
                 conditionC ? { c: 2 } : null,
                 conditionD ? { d: 3 } : null);

Algumas observações:

Ainda mais conciso

Tomando ainda mais o segundo ponto, você pode reduzi-lo da seguinte forma (como @Jamie tem fora pontas), como valores Falsas não têm próprias propriedades enumeráveis ( false, 0, NaN, null, undefined, '', com exceção document.all):

Object.assign(a, conditionB && { b: 1 },
                 conditionC && { c: 2 },
                 conditionD && { d: 3 });


11
Prefiro a solução inicial: mais fácil descobrir o que está acontecendo - não tenho certeza do que acontece com um valor primitivo (pelo menos não sem olhar as especificações).
— Axel Rauschmayer

11
Eu amo a abreviação que simplifica a lógica ternária. Isso é exatamente o que eu precisava. Obrigado!
— theUtherSide

80
const obj = {
   ...(condition) && {someprop: propvalue},
   ...otherprops
}

Demonstração ao vivo:

const obj = {
  ...(true) && {someprop: 42},
  ...(false) && {nonprop: "foo"},
  ...({}) && {tricky: "hello"},
}

console.log(obj);


7
Embora esse trecho de código possa resolver a questão, incluir uma explicação realmente ajuda a melhorar a qualidade da sua postagem. Lembre-se de que você está respondendo à pergunta dos leitores no futuro e essas pessoas podem não saber os motivos da sua sugestão de código.
— Ralf Stubner

11
O que essa resposta adiciona à resposta de Jamie Hill de dois anos antes ?
— Dan Dascalescu 21/01/19

se cond não corresponder, isso retornará indefinido.
— Mustkeem K

2
Isso funciona e possui uma sintaxe melhor do que qualquer outra resposta da IMO.
— Seph Reed

11
Uma breve explicação é a seguinte: "..." O operador de dispersão desconstrói o objeto literal e o adiciona a "obj", por exemplo, neste caso ... (true) && {someprop: 42}, todo o termo a ser desconstruído é "(true) && {someprop: 42}", neste caso o booleano é true e o termo apenas produz {someprop: 42}, que é então desconstruído e adicionado ao obj. se o boolean é falsa em vez disso, em seguida, o termo será apenas falsa, e nada vai ser desconstruída e adicionados a obj
— Qiong Wu

50

O uso de sintaxe de propagação com booleano (como sugerido aqui) não é uma sintaxe válida. A propagação só pode ser usada com iterables .

Sugiro o seguinte:

const a = {
   ...(someCondition? {b: 5}: {} )
}

2
@HossamMourad, não deveria, pois o código sugerido já foi usado em uma resposta postada há mais de 2 anos. E a primeira observação é falsa. Esta é uma sintaxe válida:{...false}
— trincot

11
@ Itai, isso não é verdade; valores primitivos são agrupados quando usados ​​com a sintaxe de dispersão. {...false}é uma sintaxe válida.
— trincot

@ Trincot, você pode fornecer referência?
— Itai Noam

3
A especificação de idioma do EcmaScript 2018, seção 12.2.6 (.8) especifica que CopyDataPropertiesé realizada no valor ( falseneste caso). Isso envolve o boxe de uma primitiva (seções 7.3.23, 7.1.13). O link que você tem para o MDN menciona essa "exceção" entre parênteses.
— Trincot

Como referência, veja também este post onde eu expiro esse mesmo assunto.
— trincot

26

Que tal usar as Propriedades Avançadas do Objeto e definir a propriedade apenas se for verdade, por exemplo:

[isConditionTrue() && 'propertyName']: 'propertyValue'

Portanto, se a condição não for atendida, ela não criará a propriedade preferida e, portanto, você poderá descartá-la. Consulte: http://es6-features.org/#ComputedPropertyNames

ATUALIZAÇÃO: É ainda melhor seguir a abordagem de Axel Rauschmayer em seu artigo de blog sobre a adição condicional de entradas em literais e matrizes de objetos ( http://2ality.com/2017/04/conditional-literal-entries.html ):

const arr = [
  ...(isConditionTrue() ? [{
    key: 'value'
  }] : [])
];

const obj = {
  ...(isConditionTrue() ? {key: 'value'} : {})
};

Me ajudou bastante.


11
Isso quase funcionará. O problema é que ele adicionará uma falsechave extra . Por exemplo, {[true && 'a']: 17, [false && 'b']: 42}is{a:17, false: 42}
— viebel 17/17/17

3
Eu encontrei uma maneira mais concisa: ...isConditionTrue() && { propertyName: 'propertyValue' }
— Dimitri Reifschneider

Melhor maneira: ... (isConditionTrue ()? {Key: 'value'}: {})
— Dimitri Reifschneider

O link do blog Axel Rauschmayer faz essa resposta. O exemplo "... insertIf (cond, 'a')" no artigo é exatamente o que eu estava procurando. Obrigado
— Joseph Simpson

21

mais simplificado,

const a = {
    ...(condition && {b: 1}) // if condition is true 'b' will be added.
}

5

Se o objetivo é fazer com que o objeto pareça independente e esteja dentro de um conjunto de chaves, você pode tentar o seguinte:

var a = new function () {
    if (conditionB)
        this.b = 5;

    if (conditionC)
        this.c = 5;

    if (conditionD)
        this.d = 5;
};

5

Se você deseja fazer esse lado do servidor (sem jquery), pode usar o lodash 4.3.0:

a = _.pickBy({ b: (someCondition? 5 : undefined) }, _.negate(_.isUndefined));

E isso funciona usando o lodash 3.10.1

a = _.pick({ b: (someCondition? 5 : undefined) }, _.negate(_.isUndefined));

Não há necessidade de lodash no ES6.
— Dan Dascalescu 21/01/19

5
var a = {
    ...(condition ? {b: 1} : '') // if condition is true 'b' will be added.
}

Espero que seja a maneira mais eficiente de adicionar uma entrada com base na condição. Para obter mais informações sobre como adicionar entradas condicionais dentro de literais de um objeto.


3
[...condition?'':['item']]isto irá adicionar item de string em array
— Wayou


11
@DanDascalescu A resposta de Jamie Hill é melhor do que a minha, eu não pensava assim e costumava ser mais um operador de operador ternário.
— precisa saber é o seguinte

4

Isso tem sido respondido há muito tempo, mas, olhando para outras idéias, eu vim com uma derivada interessante:

Atribua valores indefinidos à mesma propriedade e exclua-a posteriormente

Criar o objeto usando um construtor anónimo e sempre atribuir membros indefinidos para o mesmo manequim membro que você remove no final. Isso fornecerá uma única linha (não muito complexa, espero) por membro + 1 linha adicional no final.

var a = new function() {
    this.AlwaysPresent = 1;
    this[conditionA ? "a" : "undef"] = valueA;
    this[conditionB ? "b" : "undef"] = valueB;
    this[conditionC ? "c" : "undef"] = valueC;
    this[conditionD ? "d" : "undef"] = valueD;
    ...
    delete this.undef;
};

4

Você pode adicionar todos os seus valores indefinidos sem condição e, em seguida, usar JSON.stringifypara remover todos eles:

const person = {
  name: undefined,
  age: 22,
  height: null
}

const cleaned = JSON.parse(JSON.stringify(person));

// Contents of cleaned:

// cleaned = {
//   age: 22,
//   height: null
// }

2

Eu faria isso

var a = someCondition ? { b: 5 } : {};

Editado com uma versão de código de linha


se a condição for falsa, a é nufined, o que não está correto.
— bingjie2680

@ bingjie2680 Não está claro o que um deve ser quando someCondition é falsa. Eu apenas assumi a = undefined. Pode ser facilmente alterado com return { b: undefined };: /
— jwchang 28/07/12

@ bingjie2680 Em seguida, ele deve ser return {};em elseparte
— jwchang

11
Isso não é realmente o que você faria ... é? Quero dizer, mesmo que você condicionalmente toda a sintaxe literal, por que você não usaria o operador condicional? a = condition ? {b:5} : undefined;

3
Parabéns, você acabou de transformar três linhas simples em uma função de 7 linhas sem nenhum ganho. Não, o uso de uma função anônima não é um benefício.

2

Usando a biblioteca lodash, você pode usar _.omitBy

var a = _.omitBy({
    b: conditionB ? 4 : undefined,
    c: conditionC ? 5 : undefined,
}, _.IsUndefined)

Isso é útil quando você tem solicitações opcionais

var a = _.omitBy({
    b: req.body.optionalA,  //if undefined, will be removed
    c: req.body.optionalB,
}, _.IsUndefined)

0

Eu acho que sua primeira abordagem para adicionar membros condicionalmente é perfeitamente correta. Eu realmente não concordo com não querer ter um membro bde acom um valor de undefined. É simples o suficiente para adicionar uma undefinedverificação com o uso de um forloop com o inoperador. De qualquer forma, você pode facilmente escrever uma função para filtrar undefinedmembros.

var filterUndefined = function(obj) {
  var ret = {};
  for (var key in obj) {
    var value = obj[key];
    if (obj.hasOwnProperty(key) && value !== undefined) {
      ret[key] = value;
    }
  }
  return ret;
};

var a = filterUndefined({
  b: (conditionB? 5 : undefined),
  c: (conditionC? 5 : undefined),
  d: (conditionD? 5 : undefined),
  e: (conditionE? 5 : undefined),
  f: (conditionF? 5 : undefined),
  g: (conditionG? 5 : undefined),
});

Você também pode usar o deleteoperador para editar o objeto no local.


0

Quebrar em um objeto

Algo assim é um pouco mais limpo

 const obj = {
   X: 'dataX',
   Y: 'dataY',
   //...
 }

 const list = {
   A: true && 'dataA',
   B: false && 'dataB',
   C: 'A' != 'B' && 'dataC',
   D: 2000 < 100 && 'dataD',
   // E: conditionE && 'dataE',
   // F: conditionF && 'dataF',
   //...
 }

 Object.keys(list).map(prop => list[prop] ? obj[prop] = list[prop] : null)

Agrupar em uma matriz

Ou, se você quiser usar o método de Jamie Hill e tiver uma lista muito longa de condições, deverá escrever a ...sintaxe várias vezes. Para torná-lo um pouco mais limpo, basta envolvê-los em uma matriz e usá reduce()-los para devolvê-los como um único objeto.

const obj = {
  X: 'dataX',
  Y: 'dataY',
  //...

...[
  true && { A: 'dataA'},
  false && { B: 'dataB'},
  'A' != 'B' && { C: 'dataC'},
  2000 < 100 && { D: 'dataD'},
  // conditionE && { E: 'dataE'},
  // conditionF && { F: 'dataF'},
  //...

 ].reduce(( v1, v2 ) => ({ ...v1, ...v2 }))
}

Ou usando a map()função

const obj = {
  X: 'dataX',
  Y: 'dataY',
  //...
}

const array = [
  true && { A: 'dataA'},
  false &&  { B: 'dataB'},
  'A' != 'B' && { C: 'dataC'},
  2000 < 100 && { D: 'dataD'},
  // conditionE && { E: 'dataE'},
  // conditionF && { F: 'dataF'},
  //...

 ].map(val => Object.assign(obj, val))

0

Esta é a solução mais sucinta que posso encontrar:

var a = {};
conditionB && a.b = 5;
conditionC && a.c = 5;
conditionD && a.d = 5;
// ...

-1

Usando a biblioteca lodash, você pode usar _.merge

var a = _.merge({}, {
    b: conditionB ? 4 : undefined,
    c: conditionC ? 5 : undefined,
})
  1. Se conditionB for false& conditionC fortrue , entãoa = { c: 5 }
  2. Se a condiçãoB e a condiçãoC forem true , entãoa = { b: 4, c: 5 }
  3. Se ambas a condiçãoB e condiçãoC foremfalse , entãoa = {}

Eu recebo um resultado diferente. Estou usando lodash@^4.0.0. undefinedestão sendo incluídos no meu caso.
— JohnnyQ
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.