W JavaScript, jak warunkowo dodać członka do obiektu?

385

Chciałbym utworzyć obiekt z członkiem dodanym warunkowo. Proste podejście to:

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

Teraz chciałbym napisać bardziej idiomatyczny kod. Próbuję:

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

Ale teraz bjest członkiem, aktórego wartość jest undefined. To nie jest pożądany wynik.

Czy istnieje przydatne rozwiązanie?

Aktualizacja

Szukam rozwiązania, które mogłoby poradzić sobie z ogólną sprawą z udziałem kilku członków.

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
źródło
23
Tak jest; pierwszy bit kodu.
Paolo Bergantino,
5
Nie jestem pewien, czy istnieje coś takiego jak idiomatyczny JavaScript ...
Michael Berkowski
Czy to naprawdę ma znaczenie? Jeśli nigdy nie zdefiniowałeś a.b, pobieranie a.bi undefinedtak zwróci .
Teemu
5
@Teemu: Może to mieć znaczenie, gdy inoperator jest używany.
1
Na razie nie ma możliwości posiadania właściwości warunkowych w dosłownych obiektach, ale szkoda, że ​​nie dodają go w ES7, może to być bardzo przydatne, szczególnie w programowaniu po stronie serwera!
Ali

Odpowiedzi:

118

W czystym JavaScript nie mogę wymyślić nic bardziej idiomatycznego niż twój pierwszy fragment kodu.

Jeśli jednak użycie biblioteki jQuery nie wchodzi w rachubę, to $ .extend () powinno spełniać twoje wymagania, ponieważ, jak wynika z dokumentacji:

Niezdefiniowane właściwości nie są kopiowane.

Dlatego możesz napisać:

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

I uzyskaj oczekiwane wyniki (jeśli conditionBtak false, to bnie będą istnieć w a).

Frédéric Hamidi
źródło
21
Jest o wiele lepsza metoda, która nie wymaga jQuery. Spójrz na odpowiedź Jamie Hill.
Andrew Rasmussen,
13
@Andrew, ta odpowiedź wymaga ES6, który nie istniał w czasie, gdy pisałem mój.
Frédéric Hamidi
czy null działa w ten sam sposób? czy to musi być niezdefiniowane?
Aous1000,
To właściwie zła odpowiedź, ponieważ używa jQuery, a ten warunek trójkowy nie usunie właściwości z obiektu, po prostu ustawi właściwość jako niezdefiniowaną. Zobacz odpowiedź @lagistos na właściwy sposób, aby to zrobić,
Alexander Kim
Tak, sprawdź odpowiedź Jamiego poniżej: stackoverflow.com/a/40560953/10222449
MadMac
867

Myślę, że @InspiredJW zrobił to z ES5 i jak zauważył @trincot, użycie es6 jest lepszym podejściem. Ale możemy dodać nieco więcej cukru, używając operatora spreadu i logicznej ORAZ oceny zwarcia:

const a = {
   ...(someCondition && {b: 5})
}
Jamie Hill
źródło
2
Nie jestem pewien, czy to prawda, propozycja stwierdza Null/Undefined Are Ignored, że nie falsejest ignorowane. Transpilatorzy mogą obecnie na to pozwolić, ale czy jest to zgodne? Poniższe powinny być, {...someCondition ? {b: 5} : null}ale nie są tak zwarte.
Benjamin Dobell
23
Zapytałem, czy jest to ważne dla osób, które złożyły propozycję rozprzestrzeniania, i powiedzieli, że jest w porządku. github.com/tc39/proposal-object-rest-spread/issues/45 , cc @BenjaminDobell
김민준
73
@ Operator spreadu AlanH jest skrótem Object.assigni ma niższy priorytet niż operator &&. Ignoruje wartość bez właściwości (boolean, null, undefined, number) i dodaje wszystkie właściwości obiektu po ...miejscu. pamiętaj, że &&operator zwraca poprawną wartość, jeśli true lub false w przeciwnym razie. więc jeśli someConditionjest prawdą, {b : 5}zostanie przekazany do ...operatora, co spowoduje dodanie właściwości bdo awartości 5. jest someConditionfałszywe, falsezostanie przekazane ...operatorowi. w wyniku czego nic nie zostało dodane. to sprytne. Kocham to.
Félix Brunet,
11
Świetna odpowiedź, ale umieszczenie warunku i wynikowego obiektu w nawiasach znacznie poprawi czytelność tego przykładu. Nie wszyscy pamiętają pierwszeństwo operatora JS na pamięć.
Neurotransmitter
6
Jedynym innym problemem jest to, że nie możesz użyć tego do fałszywych boolean.
ggb667
92

Z EcmaScript2015 możesz używać Object.assign:

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

Kilka uwag:

Jeszcze bardziej zwięzłe

Biorąc drugi punkt dalej, można go skrócić następująco (jak @Jamie wskazał), jako wartości falsy nie mają własnych właściwości przeliczalny ( false, 0, NaN, null, undefined, '', z wyjątkiem document.all):

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

trincot
źródło
1
Wolę wstępne rozwiązanie: łatwiej zrozumieć, co się dzieje - nie byłbym pewien, co się stanie z prymitywną wartością (przynajmniej nie bez obejrzenia specyfikacji).
Axel Rauschmayer
1
Uwielbiam skrót, który upraszcza logikę trójskładnikową. Właśnie tego potrzebowałem. Dziękuję Ci!
theUtherSide
80
const obj = {
   ...(condition) && {someprop: propvalue},
   ...otherprops
}

Demo na żywo:

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

console.log(obj);

Lagistos
źródło
7
Ten fragment kodu może rozwiązać pytanie, ale wyjaśnienie naprawdę pomaga poprawić jakość posta. Pamiętaj, że w przyszłości odpowiadasz na pytanie czytelników, a ci ludzie mogą nie znać przyczyn Twojej sugestii kodu.
Ralf Stubner,
1
Co ta odpowiedź dodaje do odpowiedzi Jamie Hill sprzed 2 lat ?
Dan Dascalescu
jeśli cond nie pasuje, to zwróci niezdefiniowany.
Mustkeem K,
2
To działa i ma ładniejszą składnię niż jakakolwiek inna odpowiedź IMO.
Seph Reed
1
Krótkie wyjaśnienie wygląda następująco: operator rozprzestrzeniania „...” dekoduje literał obiektu i dodaje go do „obj”, np. W tym przypadku ... (prawda) i& {someprop: 42}, cały termin, który ma być zdekonstruowany, to „(true) && {someprop: 42}”, w tym przypadku wartość logiczna jest prawdziwa, a termin po prostu daje {someprop: 42}, który jest następnie dekonstruowany i dodawany do obj. jeśli boolean jest fałszywy, to termin będzie po prostu fałszywy i nic nie zostanie zdekonstruowane i dodane do obj
Qiong Wu
50

Używanie składni rozszerzonej z wartością logiczną (jak sugerowano tutaj) nie jest prawidłową składnią. Spread można stosować tylko z iterami .

Proponuję następujące:

const a = {
   ...(someCondition? {b: 5}: {} )
}
Itai Noam
źródło
2
@HossamMourad nie powinno, ponieważ sugerowany kod został już użyty w odpowiedzi opublikowanej ponad 2 lata temu. A pierwsza uwaga jest fałszywa. Jest to poprawna składnia:{...false}
trincot
1
@Itai, to nie jest prawda; prymitywne wartości są zawijane, gdy są używane ze składnią rozprzestrzeniania. {...false}jest poprawną składnią.
trincot
@trincot, czy możesz podać referencje?
Itai Noam
3
Specyfikacja języka EcmaScript 2018, sekcja 12.2.6 (.8) określa, że ​​wartość CopyDataPropertiesjest wykonywana ( falsew tym przypadku). Obejmuje to boksowanie prymitywów (sekcje 7.3.23, 7.1.13). Link do MDN wymienia ten „wyjątek” w nawiasach.
trincot
Jako odniesienie zobacz także ten post, w którym rozwijam ten sam temat.
trincot
26

A co z korzystaniem z Ulepszonych właściwości obiektu i ustawianie właściwości tylko wtedy, gdy jest to zgodne z prawdą, np .:

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

Jeśli więc warunek nie jest spełniony, nie tworzy preferowanej właściwości i dlatego możesz ją odrzucić. Zobacz: http://es6-features.org/#ComputedPropertyNames

AKTUALIZACJA: Jeszcze lepiej jest podążać za podejściem Axela Rauschmayera w jego blogu na temat warunkowego dodawania wpisów do literałów obiektów i tablic ( http://2ality.com/2017/04/conditional-literal-entries.html ):

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

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

Bardzo mi pomogło.

Dimitri Reifschneider
źródło
1
To prawie zadziała. Problem polega na tym, że doda dodatkowy falseklucz. Na przykład {[true && 'a']: 17, [false && 'b']: 42}jest{a:17, false: 42}
viebel
3
Znalazłem bardziej zwięzły sposób: ...isConditionTrue() && { propertyName: 'propertyValue' }
Dimitri Reifschneider,
Lepszy sposób: ... (isConditionTrue ()? {Key: 'value'}: {})
Dimitri Reifschneider
Link do bloga Axela Rauschmayera zawiera tę odpowiedź. Przykład „... insertIf (cond, 'a')” w artykule jest dokładnie tym, czego szukałem. Dzięki
Joseph Simpson,
20

bardziej uproszczony,

const a = {
    ...(condition && {b: 1}) // if condition is true 'b' will be added.
}
Faheel Khan
źródło
5

Jeśli celem jest, aby obiekt wyglądał na niezależny i znajdował się w jednym zestawie nawiasów klamrowych, możesz spróbować:

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

    if (conditionC)
        this.c = 5;

    if (conditionD)
        this.d = 5;
};
Casey Chu
źródło
5

Jeśli chcesz zrobić to po stronie serwera (bez jquery), możesz użyć lodash 4.3.0:

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

I to działa przy użyciu lodash 3.10.1

a = _.pick({ b: (someCondition? 5 : undefined) }, _.negate(_.isUndefined));
Mickl
źródło
Nie ma potrzeby lodash w ES6.
Dan Dascalescu
5
var a = {
    ...(condition ? {b: 1} : '') // if condition is true 'b' will be added.
}

Mam nadzieję, że jest to bardzo skuteczny sposób dodawania wpisu na podstawie warunku. Aby uzyskać więcej informacji na temat warunkowego dodawania wpisów do literałów obiektowych.

Madhankumar
źródło
3
[...condition?'':['item']]to doda element ciągu do tablicy
Wayou,
Jak ta odpowiedź jest lepsza od odpowiedzi Jamiego Hilla sprzed roku ?
Dan Dascalescu
1
@DanDascalescu Odpowiedź Jamie Hill jest lepsza niż moja odpowiedź, nie myślałem w ten sposób i byłem bardziej facetem od trójki.
Madhankumar
4

Od dawna już na nie odpowiedziano, ale patrząc na inne pomysły wpadłem na interesującą pochodną:

Przypisz niezdefiniowane wartości do tej samej właściwości, a następnie usuń ją

Utwórz obiekt za pomocą anonimowego konstruktora i zawsze przypisuj niezdefiniowane elementy do tego samego elementu zastępczego, który usuwasz na samym końcu. To da ci jedną linię (mam nadzieję, że nie jest zbyt złożona) na członka + 1 dodatkową linię na końcu.

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
źródło
4

Możesz dodać wszystkie swoje niezdefiniowane wartości bez warunków, a następnie użyć ich JSON.stringifydo usunięcia wszystkich:

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

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

// Contents of cleaned:

// cleaned = {
//   age: 22,
//   height: null
// }
Milad
źródło
2

Zrobiłbym to

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

Edytowane z jedną wersją kodu linii

jwchang
źródło
jeśli warunek jest fałszywy, a jest wstępnie zdefiniowany, co jest niepoprawne.
bingjie2680,
@ bingjie2680 To nie jest jasne, co to powinno być, gdy someCondition jest fałszywe. Właśnie założyłem . Można go łatwo zmienić za pomocą : /a = undefinedreturn { b: undefined };
jwchang
@ bingjie2680 to powinno być return {};w elseczęści
jwchang
1
Tak naprawdę nie zrobiłbyś tego ... prawda? Chodzi mi o to, że nawet jeśli miałbyś uzależnić całą składnię literalną tak, jak masz, dlaczego nie miałbyś używać operatora warunkowego? a = condition ? {b:5} : undefined;
3
Gratulacje, właśnie zamieniłeś trzy proste linie w funkcję 7-liniową bez żadnego zysku. Nie, korzystanie z anonimowej funkcji nie jest zaletą.
2

Korzystając z biblioteki lodash, możesz użyć _.omitBy

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

Jest to przydatne, gdy masz opcjonalne żądania

var a = _.omitBy({
    b: req.body.optionalA,  //if undefined, will be removed
    c: req.body.optionalB,
}, _.IsUndefined)
htafoya
źródło
0

Myślę, że twoje pierwsze podejście do warunkowego dodawania członków jest w porządku. Naprawdę nie zgadzam się z tym, że nie chcę mieć członka bo awartości undefined. Wystarczy dodać undefinedczek z użyciem forpętli z inoperatorem. Ale w każdym razie możesz łatwo napisać funkcję do odfiltrowywania undefinedczłonków.

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

Możesz także użyć deleteoperatora do edycji obiektu na miejscu.

Junchi
źródło
0

Zawiń w obiekt

Coś takiego jest nieco czystsze

 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)

Zawiń w tablicę

Lub jeśli chcesz skorzystać z metody Jamiego Hilla i mieć bardzo długą listę warunków, musisz napisać ...składnię wiele razy. Aby było trochę czystsze, możesz po prostu owinąć je w tablicę, a następnie użyć, reduce()aby zwrócić je jako pojedynczy obiekt.

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

Lub używając map()funkcji

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
źródło
0

To najbardziej zwięzłe rozwiązanie, jakie mogę wymyślić:

var a = {};
conditionB && a.b = 5;
conditionC && a.c = 5;
conditionD && a.d = 5;
// ...
pajęczak
źródło
-1

Korzystając z biblioteki lodash, możesz użyć _.merge

var a = _.merge({}, {
    b: conditionB ? 4 : undefined,
    c: conditionC ? 5 : undefined,
})
  1. Jeśli warunek B to false& warunek C to true, toa = { c: 5 }
  2. Jeśli oba warunki B i C są true, toa = { b: 4, c: 5 }
  3. Jeśli oba warunki B i C są false, toa = {}
użytkownik3437231
źródło
Otrzymuję inny wynik. Używam lodash@^4.0.0. undefinedsą uwzględnione w moim przypadku.
JohnnyQ,