En Javascript, comment ajouter conditionnellement un membre à un objet?

388

Je voudrais créer un objet avec un membre ajouté conditionnellement. L'approche simple est:

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

Maintenant, je voudrais écrire un code plus idiomatique. J'essaie:

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

Mais maintenant, best un membre adont la valeur est undefined. Ce n'est pas le résultat souhaité.

Existe-t-il une solution pratique?

Mise à jour

Je recherche une solution qui pourrait traiter le cas général avec plusieurs membres.

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
la source
23
Oui il y a; le premier bit de code.
Paolo Bergantino
6
Pas sûr qu'il y ait une telle chose comme JavaScript idiomatique ...
Michael Berkowski
Est-ce vraiment important? Si vous n'avez jamais défini a.b, la récupération a.breviendrait de undefinedtoute façon.
Teemu
6
@Teemu: Cela peut être important lorsque l' inopérateur est utilisé.
2
Il n'y a aucun moyen d'avoir des propriétés conditionnelles dans les objets littéraux pour l'instant, mais j'aimerais qu'ils l'ajoutent dans ES7, cela pourrait être très pratique, surtout dans la programmation côté serveur!
Ali

Réponses:

118

En pur Javascript, je ne vois rien de plus idiomatique que votre premier extrait de code.

Si, cependant, l'utilisation de la bibliothèque jQuery n'est pas hors de question, alors $ .extend () devrait répondre à vos exigences car, comme le dit la documentation:

Les propriétés non définies ne sont pas copiées.

Par conséquent, vous pouvez écrire:

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

Et obtenez les résultats que vous attendez (si conditionBc'est le cas false, alors bils n'existeront pas a).

Frédéric Hamidi
la source
21
Il existe une bien meilleure méthode qui ne nécessite pas jQuery. Jetez un œil à la réponse de Jamie Hill.
Andrew Rasmussen
13
@Andrew, cette réponse nécessite ES6, qui n'existait pas au moment où j'ai écrit la mienne.
Frédéric Hamidi
null fonctionne-t-il de la même manière? ou doit-il être indéfini?
Aous1000
C'est en fait une mauvaise réponse, car il utilise jQuery et cette condition ternaire ne supprimera pas une propriété d'un objet, cela définirait simplement une propriété comme non définie. Voir la réponse @lagistos pour la bonne façon de le faire,
Alexander Kim
Oui, consultez la réponse de Jamie ci-dessous: stackoverflow.com/a/40560953/10222449
MadMac
874

Je pense que @InspiredJW l'a fait avec ES5, et comme l'a souligné @trincot, utiliser es6 est une meilleure approche. Mais nous pouvons ajouter un peu plus de sucre, en utilisant l'opérateur d'étalement, et une évaluation logique ET en court-circuit:

const a = {
   ...(someCondition && {b: 5})
}
Jamie Hill
la source
2
Je ne suis pas sûr que ce soit correct, la proposition des États Null/Undefined Are Ignored, il ne dit pas falseest ignoré. Les transpileurs peuvent autoriser cela à l'heure actuelle, mais est-ce conforme? Ce qui suit devrait être {...someCondition ? {b: 5} : null}mais n'est pas aussi compact.
Benjamin Dobell
24
J'ai demandé si cela était valable pour les personnes qui ont fait une proposition de propagation et ils ont dit que c'était bien. github.com/tc39/proposal-object-rest-spread/issues/45 , cc @BenjaminDobell
김민준
73
L'opérateur @AlanH spread est comme un raccourci de Object.assignet a une priorité plus faible que l'opérateur &&. Il ignore la valeur sans propriété (booléen, null, non défini, nombre) et ajoute toutes les propriétés de l'objet après la mise ...en place. rappelez-vous que l' &&opérateur retourne la bonne valeur si vrai, ou faux sinon. donc si someConditionest vrai, {b : 5}sera passé à l' ...opérateur, ce qui entraînera l'ajout de la propriété bà aavec valeur 5. is someConditionest false, falsesera transmis à l' ...opérateur. résultant en rien ajouté. c'est intelligent. J'aime cela.
Félix Brunet
11
Excellente réponse, mais mettre la condition et l'objet résultant en cours de répartition entre parenthèses améliorera considérablement la lisibilité de cet exemple. Tout le monde ne se souvient pas par cœur de la priorité des opérateurs JS.
Neurotransmetteur du
6
Le seul autre problème est que vous ne pouvez pas l'utiliser pour les faux booléens.
ggb667
92

Avec EcmaScript2015, vous pouvez utiliser Object.assign:

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

Quelques remarques:

Encore plus concis

En prenant le deuxième point plus loin, vous pouvez le raccourcir comme suit (comme @Jamie a en pointe), en tant que valeurs falsy ont pas propres propriétés dénombrables ( false, 0, NaN, null, undefined, '', à l' exception document.all):

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

trincot
la source
1
Je préfère la solution initiale: plus facile à comprendre ce qui se passe - je ne serais pas sûr de ce qui se passe avec une valeur primitive (du moins pas sans regarder la spécification).
Axel Rauschmayer
1
J'adore la sténographie qui simplifie la logique ternaire. C'est exactement ce dont j'avais besoin. Je vous remercie!
theUtherSide
80
const obj = {
   ...(condition) && {someprop: propvalue},
   ...otherprops
}

Démo en direct:

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

console.log(obj);

Lagistos
la source
7
Bien que cet extrait de code puisse résoudre la question, y compris une explication aide vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondrez à la question pour les lecteurs à l'avenir, et ces personnes pourraient ne pas connaître les raisons de votre suggestion de code.
Ralf Stubner
1
Qu'est-ce que cette réponse ajoute à la réponse de Jamie Hill de 2 ans plus tôt ?
Dan Dascalescu
si cond ne correspond pas, cela retournera indéfini.
Mustkeem K
2
Cela fonctionne, et il a une syntaxe plus agréable que n'importe laquelle des autres réponses IMO.
Seph Reed
1
Une courte explication se présente comme suit: "..." l'opérateur de propagation déconstruit l'objet littéral et l'ajoute à "obj" par exemple dans ce cas ... (vrai) && {someprop: 42}, le terme entier qui doit être déconstruit est "(true) && {someprop: 42}", dans ce cas le booléen est vrai et le terme donne juste {someprop: 42} qui est ensuite déconstruit et ajouté dans obj. si le booléen est faux à la place, alors le terme sera juste faux, et rien ne sera déconstruit et ajouté dans obj
Qiong Wu
50

L'utilisation de la syntaxe étendue avec booléen (comme suggéré ici) n'est pas une syntaxe valide. La propagation ne peut être utilisée qu'avec des itérables .

Je suggère ce qui suit:

const a = {
   ...(someCondition? {b: 5}: {} )
}
Itai Noam
la source
2
@HossamMourad, cela ne devrait pas, car le code suggéré a déjà été utilisé dans une réponse publiée il y a plus de 2 ans. Et la première remarque est fausse. Ceci est une syntaxe valide:{...false}
trincot
1
@Itai, ce n'est pas vrai; les valeurs primitives sont encapsulées lorsqu'elles sont utilisées avec la syntaxe étalée. {...false}est une syntaxe valide.
trincot
@trincot, pouvez-vous s'il vous plaît fournir une référence?
Itai Noam
3
La spécification du langage EcmaScript 2018, section 12.2.6 (.8) spécifie que CopyDataPropertiesla valeur est exécutée ( falsedans ce cas). Cela implique la mise en boîte d'une primitive (sections 7.3.23, 7.1.13). Le lien que vous avez vers MDN mentionne cette "exception" entre parenthèses.
trincot
À titre de référence, voir également cet article où je développe sur ce même sujet.
trincot
26

Qu'en est-il de l'utilisation des propriétés d'objet améliorées et ne définissez la propriété que si elle est vraie, par exemple:

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

Donc, si la condition n'est pas remplie, elle ne crée pas la propriété préférée et vous pouvez donc la supprimer. Voir: http://es6-features.org/#ComputedPropertyNames

MISE À JOUR: Il est encore mieux de suivre l'approche d'Axel Rauschmayer dans son article de blog sur l'ajout conditionnel d'entrées à l'intérieur des littéraux et des tableaux d'objets ( http://2ality.com/2017/04/conditional-literal-entries.html ):

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

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

Cela m'a beaucoup aidé.

Dimitri Reifschneider
la source
1
Cela fonctionnera presque. Le problème est qu'il ajoutera une falseclé supplémentaire . Par exemple, {[true && 'a']: 17, [false && 'b']: 42}est{a:17, false: 42}
viebel
3
J'ai trouvé un moyen plus concis: ...isConditionTrue() && { propertyName: 'propertyValue' }
Dimitri Reifschneider
Meilleure façon: ... (isConditionTrue ()? {Key: 'value'}: {})
Dimitri Reifschneider
Le lien du blog Axel Rauschmayer apporte cette réponse. L'exemple "... insertIf (cond, 'a')" dans l'article est exactement ce que je cherchais. Merci
Joseph Simpson
21

plus simplifié,

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

Si l'objectif est de faire en sorte que l'objet apparaisse autonome et se trouve dans un ensemble d'accolades, vous pouvez essayer ceci:

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

    if (conditionC)
        this.c = 5;

    if (conditionD)
        this.d = 5;
};
Casey Chu
la source
5

Si vous souhaitez faire cela côté serveur (sans jquery), vous pouvez utiliser lodash 4.3.0:

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

Et cela fonctionne en utilisant lodash 3.10.1

a = _.pick({ b: (someCondition? 5 : undefined) }, _.negate(_.isUndefined));
Mickl
la source
Pas besoin de lodash dans ES6.
Dan Dascalescu
5
var a = {
    ...(condition ? {b: 1} : '') // if condition is true 'b' will be added.
}

J'espère que c'est le moyen le plus efficace d'ajouter une entrée en fonction de la condition. Pour plus d'informations sur l'ajout conditionnel d'entrées à l'intérieur d'un littéral d'objet.

Madhankumar
la source
3
[...condition?'':['item']]cela ajoutera un élément de chaîne dans le tableau
Wayou
Comment cette réponse est-elle meilleure que la réponse de Jamie Hill d'un an plus tôt ?
Dan Dascalescu
1
@DanDascalescu La réponse de Jamie Hill est meilleure que ma réponse, je ne pensais pas de cette façon et j'étais plutôt un opérateur ternaire.
Madhankumar
4

Cela a longtemps été répondu, mais en regardant d'autres idées, j'ai trouvé un dérivé intéressant:

Attribuez des valeurs non définies à la même propriété et supprimez-la ensuite

Créez votre objet à l'aide d'un constructeur anonyme et affectez toujours des membres non définis au même membre factice que vous supprimez à la toute fin. Cela vous donnera une seule ligne (pas trop complexe j'espère) par membre + 1 ligne supplémentaire à la fin.

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
la source
4

Vous pouvez ajouter toutes vos valeurs indéfinies sans condition, puis utiliser JSON.stringifypour les supprimer toutes:

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

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

// Contents of cleaned:

// cleaned = {
//   age: 22,
//   height: null
// }
Milad
la source
2

Je ferais ça

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

Modifié avec une version de code de ligne

jwchang
la source
si la condition est fausse, a est nudefined, ce qui n'est pas correct.
bingjie2680
@ bingjie2680 Il est pas clair ce qu'il a devrait être quand conditionquelconque est faux. Je viens de supposer a = undefined. Il peut être facilement modifié avec return { b: undefined };: /
jwchang
@ bingjie2680 Alors ça devrait être return {};en elsepartie
jwchang
1
Ce n'est pas vraiment ce que vous feriez ... n'est-ce pas? Je veux dire que même si vous deviez rendre la syntaxe littérale entière conditionnelle comme vous l'avez fait, pourquoi n'utiliseriez-vous pas l'opérateur conditionnel? a = condition ? {b:5} : undefined;
3
Félicitations, vous venez de transformer trois lignes simples en une fonction à 7 lignes sans gain. Non, l'utilisation d'une fonction anonyme n'est pas un avantage.
2

En utilisant la bibliothèque lodash, vous pouvez utiliser _.omitBy

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

Cela est pratique lorsque vous avez des demandes facultatives

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

Je pense que votre première approche pour ajouter des membres conditionnellement est parfaitement bien. Je ne suis pas d' accord avec ne pas vouloir d'avoir un membre bde aune valeur de undefined. C'est assez simple pour ajouter une undefinedvérification avec l'utilisation d'une forboucle avec l' inopérateur. Mais de toute façon, vous pouvez facilement écrire une fonction pour filtrer les undefinedmembres.

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

Vous pouvez également utiliser l' deleteopérateur pour modifier l'objet en place.

Yunchi
la source
0

Envelopper dans un objet

Quelque chose comme ça est un peu plus propre

 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)

Envelopper dans un tableau

Ou si vous souhaitez utiliser la méthode de Jamie Hill et avoir une très longue liste de conditions, vous devez écrire la ...syntaxe plusieurs fois. Pour le rendre un peu plus propre, vous pouvez simplement les envelopper dans un tableau, puis les utiliser reduce()pour les renvoyer comme un seul objet.

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 en utilisant la map()fonction

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
la source
0

C'est la solution la plus succincte que je puisse trouver:

var a = {};
conditionB && a.b = 5;
conditionC && a.c = 5;
conditionD && a.d = 5;
// ...
sous-arachnide
la source
0

Définissez un var par letet attribuez simplement une nouvelle propriété

let msg = {
    to: "[email protected]",
    from: "[email protected]",
    subject: "Contact form",    
};

if (file_uploaded_in_form) { // the condition goes here
    msg.attachments = [ // here 'attachments' is the new property added to msg Javascript object
      {
        content: "attachment",
        filename: "filename",
        type: "mime_type",
        disposition: "attachment",
      },
    ];
}

Maintenant, le msgdevenir

{
    to: "[email protected]",
    from: "[email protected]",
    subject: "Contact form",
    attachments: [
      {
        content: "attachment",
        filename: "filename",
        type: "mime_type",
        disposition: "attachment",
      },
    ]
}

À mon avis, c'est une solution très simple et facile.

SohelAhmedM
la source
-1

En utilisant la bibliothèque lodash, vous pouvez utiliser _.merge

var a = _.merge({}, {
    b: conditionB ? 4 : undefined,
    c: conditionC ? 5 : undefined,
})
  1. Si conditionB est false& conditionC esttrue , alorsa = { c: 5 }
  2. Si les deux conditions B et condition C sont true, alorsa = { b: 4, c: 5 }
  3. Si les deux conditions B et condition C le sont false, alorsa = {}
user3437231
la source
J'obtiens un résultat différent. J'utilise lodash@^4.0.0. undefinedsont inclus dans mon cas.
JohnnyQ