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),
 };
viebel
fonte
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).

Frédéric Hamidi
fonte
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})
}
Jamie Hill
fonte
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 });

trincot
fonte
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);

Lagistos
fonte
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}: {} )
}
Itai Noam
fonte
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.

Dimitri Reifschneider
fonte
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.
}
Faheel Khan
fonte
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;
};
Casey Chu
fonte
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));
Mickl
fonte
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.

Madhankumar
fonte
3
[...condition?'':['item']]isto irá adicionar item de string em array
Wayou
Como essa resposta é melhor do que a resposta de Jamie Hill de um ano antes ?
Dan Dascalescu 21/01/19
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;
};
Robert Koritnik
fonte
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
// }
Milad
fonte
2

Eu faria isso

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

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

jwchang
fonte
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)
htafoya
fonte
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.

Yunchi
fonte
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))
Dedy Abdillah
fonte
0

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

var a = {};
conditionB && a.b = 5;
conditionC && a.c = 5;
conditionD && a.d = 5;
// ...
subaracnídeo
fonte
-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 = {}
user3437231
fonte
Eu recebo um resultado diferente. Estou usando lodash@^4.0.0. undefinedestão sendo incluídos no meu caso.
JohnnyQ