Filtruj właściwości obiektu według klucza w ES6


266

Powiedzmy, że mam obiekt:

{
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
}

Chcę utworzyć inny obiekt, filtrując obiekt powyżej, więc mam coś takiego.

 {
    item1: { key: 'sdfd', value:'sdfd' },
    item3: { key: 'sdfd', value:'sdfd' }
 }

Szukam czystego sposobu na osiągnięcie tego za pomocą Es6, więc operatorzy rozprzestrzeniania są dla mnie dostępni.


ES6 nie ma operatorów rozprzestrzeniania obiektów i nie potrzebujesz ich tutaj
Bergi,


@DanDascalescu Ale ta odpowiedź daje ES6 sposób na osiągnięcie tego, o co prosi OP, prawda?
Jonathan H

Co jeśli chciałbym filtrować według klucza / wartości?
jmchauv

Odpowiedzi:


503

Jeśli masz listę dozwolonych wartości, możesz łatwo zachować je w obiekcie, używając:

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = Object.keys(raw)
  .filter(key => allowed.includes(key))
  .reduce((obj, key) => {
    obj[key] = raw[key];
    return obj;
  }, {});

console.log(filtered);

Wykorzystuje to:

  1. Object.keysaby wyświetlić wszystkie właściwości w raw(oryginalne dane), a następnie
  2. Array.prototype.filter wybierz klucze znajdujące się na liście dozwolonych, używając
    1. Array.prototype.includes aby upewnić się, że są obecni
  3. Array.prototype.reduce zbudować nowy obiekt z tylko dozwolonymi właściwościami.

Spowoduje to utworzenie płytkiej kopii z dozwolonymi właściwościami (ale nie skopiuje samych właściwości).

Możesz także użyć operatora rozkładania obiektów, aby utworzyć serię obiektów bez ich mutowania (dzięki rjerue za wspomnienie o tym ):

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = Object.keys(raw)
  .filter(key => allowed.includes(key))
  .reduce((obj, key) => {
    return {
      ...obj,
      [key]: raw[key]
    };
  }, {});

console.log(filtered);

Na potrzeby ciekawostek, jeśli chcesz usunąć niechciane pola z oryginalnych danych (czego nie poleciłbym robić, ponieważ wiąże się to z brzydkimi mutacjami), możesz odwrócić includeskontrolę w następujący sposób:

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

Object.keys(raw)
  .filter(key => !allowed.includes(key))
  .forEach(key => delete raw[key]);

console.log(raw);

Podaję ten przykład, aby pokazać rozwiązanie oparte na mutacjach, ale nie sugeruję, aby go używać.


1
Dzięki, że działało świetnie. Znalazłem również podejście, używając składni dekonstrukcyjnej. IE: const {item1, item3} = raw const newObject = {item1, item3}
29

2
Dekonstrukcja będzie działać (dobrze), ale jest to czas kompilacji. Nie można ustawić go jako dynamicznej listy właściwości ani podać żadnych złożonych reguł (na przykład w pętli mogą być dołączone wywołania zwrotne sprawdzania poprawności).
ssube

3
Nie mogę wystarczająco głosować! Dobra robota do używania filtru i zmniejszania, a nie budowania innego obiektu z pętli for. I niesamowite, że wyraźnie oddzieliłeś wersje niezmienne i zmienne. +1
Sukima,

3
Szybkie ostrzeżenie: Array.prototype.includesnie jest częścią ES6. Został wprowadzony w ECMAScript 2016 (ES7).
Vineet,

3
Jeśli chcesz dokonać redukcji w niezmienny sposób, możesz również zastąpić zawartość funkcji
znakiem

93

Jeśli nie masz nic przeciwko korzystaniu ze składni ES6, uważam, że to najczystszy sposób, aby to zrobić, jak zauważono tutaj i tutaj :

const data = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const { item2, ...newData } = data;

Teraz newDatazawiera:

{
  item1: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

Lub, jeśli klucz jest przechowywany jako ciąg:

const key = 'item2';
const { [key]: _, ...newData } = data;

W tym drugim przypadku [key]jest konwertowany, item2ale ponieważ używasz constprzypisania, musisz podać nazwę przypisania. _reprezentuje wartość wyrzucania.

Bardziej ogólnie:

const { item2, ...newData } = data; // Assign item2 to item2
const { item2: someVarName, ...newData } = data; // Assign item2 to someVarName
const { item2: _, ...newData } = data; // Assign item2 to _
const { ['item2']: _, ...newData } = data; // Convert string to key first, ...

To nie tylko zmniejsza twoją operację do jednej linijki, ale także nie wymaga znajomości innych kluczy (tych, które chcesz zachować).


5
_reprezentuje wartość odrzucenia” skąd to się bierze? Pierwszy raz to widzę
yhabib

5
Uważam, że jest to konwencja przyjęta przez społeczność JS. Jest _to po prostu poprawna nazwa zmiennej, która może być używana w JS, ale ponieważ jest prawie bezimienna, naprawdę nie powinna być używana w ten sposób, jeśli chcesz przekazać zamiar. Został więc przyjęty jako sposób na oznaczenie zmiennej, na której ci nie zależy. Oto dalsza dyskusja na ten temat: stackoverflow.com/questions/11406823/...
Ryan H.

2
Jest to znacznie bardziej uporządkowane niż zaakceptowana odpowiedź, pozwala uniknąć narzutu związanego z tworzeniem nowej tablicy za pomocą Object.keys () oraz narzutu związanego z iterowaniem tablicy za pomocą filteri reduce.
ericsoco

3
@yhabib to _nie ma znaczenia, to tylko zmienna nazwa, możesz zmienić jej nazwę na dowolną
Vic

1
@Gerrat Nie sądzę, że uogólnione rozwiązanie byłoby trywialną jednostronną sprawą. W tym celu użyłbym omitfunkcji lodash : lodash.com/docs/4.17.10#omit lub jednego z innych podanych tutaj rozwiązań.
Ryan H.,

42

Najczystszym sposobem jest znalezienie Lodash # pick

const _ = require('lodash');

const allowed = ['item1', 'item3'];

const obj = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
}

const filteredObj = _.pick(obj, allowed)

3
Należy zauważyć, że w tym celu należy pobrać dodatkową zależność projektu od środowiska wykonawczego.
S. Esteves,

1
_.omit (obj, ["item2"]) - lodash.omit jest przeciwieństwem lodash.pick
Alexander Solovyev

29

Nic, co nie zostało powiedziane wcześniej, ale połączenie kilku odpowiedzi z ogólną odpowiedzią ES6:

const raw = {
  item1: { key: 'sdfd', value: 'sdfd' },
  item2: { key: 'sdfd', value: 'sdfd' },
  item3: { key: 'sdfd', value: 'sdfd' }
};

const filteredKeys = ['item1', 'item3'];

const filtered = filteredKeys
  .reduce((obj, key) => ({ ...obj, [key]: raw[key] }), {});

console.log(filtered);


1
jest to o wiele prostsze i
wydajniejsze

26

To kolejne rozwiązanie w jednej linii Modern JS bez zewnętrznych bibliotek .

Bawiłem się funkcją „ Destrukturyzacja ”:

const raw = {
    item1: { key: 'sdfd', value: 'sdfd' },
    item2: { key: 'sdfd', value: 'sdfd' },
    item3: { key: 'sdfd', value: 'sdfd' }
  };
var myNewRaw = (({ item1, item3}) => ({ item1, item3 }))(raw);
console.log(myNewRaw);


2
var myNewRaw = (({ item1, item3}) => ({ item1, item3 }))(raw);Problem ze
składnią

1
Jest tu kilka rzeczy skondensowanych: Po pierwsze, jest deklaracja funkcji. jest to funkcja strzałki (pobiera obiekt i zwraca obiekt). Jest to to samo, co funkcja (obj) {return obj;}. po drugie, ES6 pozwala na destrukcję przyszłości. W mojej deklaracji funkcji niszczę mój obiekt {item1, item3}. A ostatnią rzeczą jest to, że sam wzywam swoją funkcję. Możesz na przykład użyć funkcji samodzielnego wywołania, aby zarządzać zasięgiem. Ale tutaj było tylko zagęszczenie kodu. Mam nadzieję, że to jasne. Jeśli coś przegapię, dodaj więcej.
Novy

5
jest to preferowane nowoczesne podejście
imho

20

Możesz teraz uczynić go krótszym i prostszym za pomocą metody Object.fromEntries (sprawdź obsługę przeglądarki):

const raw = { item1: { prop:'1' }, item2: { prop:'2' }, item3: { prop:'3' } };

const allowed = ['item1', 'item3'];

const filtered = Object.fromEntries(
   Object.entries(raw).filter(
      ([key, val])=>allowed.includes(key)
   )
);

czytaj więcej o: Object.fromEntries


1
To jest teraz najlepszy sposób, jaki myślę. Znacznie bardziej intuicyjny niż redukujący.
Damon

10

Możesz dodać ogólny ofilter(zaimplementowany z ogólnym oreduce), aby łatwo filtrować obiekty w taki sam sposób, jak tablice -

const oreduce = (f, acc, o) =>
  Object
    .entries (o)
    .reduce
      ( (acc, [ k, v ]) => f (acc, v, k, o)
      , acc
      )

const ofilter = (f, o) =>
  oreduce
    ( (acc, v, k, o)=>
        f (v, k, o)
          ? Object.assign (acc, {[k]: v})
          : acc
    , {}
    , o
    )

Widzimy, że działa tutaj -

const data =
  { item1: { key: 'a', value: 1 }
  , item2: { key: 'b', value: 2 }
  , item3: { key: 'c', value: 3 }
  }

console.log
  ( ofilter
      ( (v, k) => k !== 'item2'
      , data
      )
      // [ { item1: { key: 'a', value: 1 } }
      // , { item3: { key: 'c', value: 3 } }
      // ]

  , ofilter
      ( x => x.value === 3
      , data
      )
      // [ { item3: { key: 'c', value: 3 } } ]
  )

Sprawdź wyniki we własnej przeglądarce poniżej -

Te dwie funkcje można zaimplementować na wiele sposobów. Zdecydowałem się dołączyć do Array.prototype.reduceśrodka, oreduceale równie łatwo można napisać wszystko od zera


Podoba mi się twoje rozwiązanie, ale nie wiem, o ile jaśniejsze / bardziej wydajne w porównaniu do tego .
Jonathan H

3
Oto punkt odniesienia pokazujący, że Twoje rozwiązanie jest najszybsze.
Jonathan H

W oreduce, pierwszy accjest zasłonięty .reduce(acc, k), a ofilterwewnątrz ozasłonięty - oreducenazywany jest również zmienną o nazwie o- która jest która?
Jarrod Mosen

w ofilterzmiennej ojest zacieniony, ale zawsze wskazuje na to samo wejście var; dlatego jest tu zapisywany w tle, ponieważ jest taki sam - akumulator ( acc) jest również zapisywany w tle, ponieważ wyraźniej pokazuje, jak dane przemieszczają się przez lambda; accnie zawsze jest tak samo wiążące , ale zawsze reprezentuje bieżący trwały stan wyniku obliczeniowego, który chcemy zwrócić
Dziękujemy

Chociaż kod działa dobrze, a metoda jest bardzo dobra, zastanawiam się, jaka pomoc w pisaniu takiego kodu jest dla kogoś, kto oczywiście potrzebuje pomocy z javascript. Zależy mi na zwięzłości, ale jest prawie tak zwarta jak zminimalizowany kod.
Paul G Mihai,

7

Tak to ostatnio zrobiłem:

const dummyObj = Object.assign({}, obj);
delete dummyObj[key];
const target = Object.assign({}, {...dummyObj});

Hm Wygląda na to, że miksujesz starą i nową składnię. Object.assign == ...Możesz pisać const dummyObj = { ...obj }i const target = { ...dummyObj }. Co więcej, to drugie nie jest wcale konieczne, ponieważ później możesz bezpośrednio pracować z dummyObj.
Andy,

7

ok, a co z tym linkiem

    const raw = {
      item1: { key: 'sdfd', value: 'sdfd' },
      item2: { key: 'sdfd', value: 'sdfd' },
      item3: { key: 'sdfd', value: 'sdfd' }
    };

    const filteredKeys = ['item1', 'item3'];

    const filtered = Object.assign({}, ...filteredKeys.map(key=> ({[key]:raw[key]})));

2
Najbardziej niesamowite rozwiązanie spośród wszystkich odpowiedzi. +1
Gergő Horváth

co jeśli wiesz tylko klucze, które chcesz usunąć ... a nie te klucze, które chcesz zachować?
Jason

6

Odpowiedzi tutaj są zdecydowanie odpowiednie, ale są nieco powolne, ponieważ wymagają zapętlenia białej listy dla każdej właściwości w obiekcie. Poniższe rozwiązanie jest znacznie szybsze w przypadku dużych zestawów danych, ponieważ zapętla się na białej liście tylko raz:

const data = {
  allowed1: 'blah',
  allowed2: 'blah blah',
  notAllowed: 'woah',
  superSensitiveInfo: 'whooooah',
  allowed3: 'bleh'
};

const whitelist = ['allowed1', 'allowed2', 'allowed3'];

function sanitize(data, whitelist) {
    return whitelist.reduce(
      (result, key) =>
        data[key] !== undefined
          ? Object.assign(result, { [key]: data[key] })
          : result,
      {}
    );
  }

  sanitize(data, whitelist)

5

Piggybacking na odpowiedzi ssube .

Oto wersja wielokrotnego użytku.

Object.filterByKey = function (obj, predicate) {
  return Object.keys(obj)
    .filter(key => predicate(key))
    .reduce((out, key) => {
      out[key] = obj[key];
      return out;
    }, {});
}

Aby to nazwać, użyj

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

var filtered = Object.filterByKey(raw, key => 
  return allowed.includes(key));
});

console.log(filtered);

Piękną rzeczą w funkcjach strzałek ES6 jest to, że nie trzeba podawać allowedparametru.


4

Możesz zrobić coś takiego:

const base = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const filtered = (
    source => { 
        with(source){ 
            return {item1, item3} 
        } 
    }
)(base);

// one line
const filtered = (source => { with(source){ return {item1, item3} } })(base);

Działa to, ale nie jest bardzo jasne, a withinstrukcja nie jest zalecana ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with ).


4

Zamiast tego filtermożna uzyskać prostsze rozwiązanie bez użyciaObject.entries()Object.keys()

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = Object.entries(raw).reduce((acc,elm)=>{
  const [k,v] = elm
  if (allowed.includes(k)) {
    acc[k] = v 
  }
  return acc
},{})

3

Istnieje wiele sposobów na osiągnięcie tego. W zaakceptowanej odpowiedzi zastosowano podejście Keys-Filter-Reduce, które nie jest najbardziej wydajne.

Zamiast tego użyj for...inpętli, aby przejść przez klucze obiektu lub pętli przez dozwolone klucze, i następnie tworzenia nowego obiektu wynosi około 50% bardziej wydajnych się .

const obj = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const keys = ['item1', 'item3'];

function keysReduce (obj, keys) {
  return keys.reduce((acc, key) => {
    if(obj[key] !== undefined) {
      acc[key] = obj[key];
    }
    return acc;
  }, {});
};

function forInCompose (obj, keys) {
  const returnObj = {};
  for (const key in obj) {
    if(keys.includes(key)) {
      returnObj[key] = obj[key]
    }
  };
  return returnObj;
};

keysReduce(obj, keys);   // Faster if the list of allowed keys are short
forInCompose(obj, keys); // Faster if the number of object properties are low

za. Widzieć jsPerf, aby zapoznać się z testami prostego przypadku użycia. Wyniki będą się różnić w zależności od przeglądarki.


3

Możesz usunąć klucz specjalny ze swojego obiektu

items={
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
}

// Example 1
var key = "item2";
delete items[key]; 

// Example 2
delete items["item2"];

// Example 3
delete items.item2;

3
const filteredObject = Object.fromEntries(Object.entries(originalObject).filter(([key, value]) => key !== uuid))

2
Istnieją inne odpowiedzi, które dostarczają pytania OP, i zostały opublikowane jakiś czas temu. Publikując odpowiedź, upewnij się, że dodałeś nowe rozwiązanie lub znacznie lepsze wyjaśnienie, szczególnie w przypadku odpowiedzi na starsze pytania.
help-info.de

2

Prosta droga! Aby to zrobić

const myData = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};
const{item1,item3}=myData
const result =({item1,item3})


Tak naprawdę nic tu nie filtrujesz, tylko niszczy podane dane. Ale co się stanie, gdy dane się zmienią?
Idris Dopico Peña

2

Inne rozwiązanie wykorzystujące „nową” Array.reducemetodę:

const raw = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

const allowed = ['item1', 'item3'];

const filtered = allowed.reduce((obj, key) => { 
  obj[key] = raw[key]; 
  return obj 
}, {})

console.log(filtered);

Demonstracja w tym skrzypcach ...


Ale podoba mi się rozwiązanie w tej odpowiedzi, które używa i :Object.fromEntries Array.filterArray.includes

const object = Object.fromEntries( Object.entries(raw).filter(([key, value]) => allowed.includes(key)) );

Demonstracja w tym skrzypcach ...


Wilt, twoje drugie podejście jest dokładnym duplikatem tej odpowiedzi podanej w zeszłym roku: stackoverflow.com/a/56081419/5522000
Art Schmidt

@ArtSchmidt Dziękuję za komentarz: Brakowało tego na długiej liście. Zamiast tego odniosę się do tej odpowiedzi.
Wilt

1

OK, a co z tym:

const myData = {
  item1: { key: 'sdfd', value:'sdfd' },
  item2: { key: 'sdfd', value:'sdfd' },
  item3: { key: 'sdfd', value:'sdfd' }
};

function filteredObject(obj, filter) {
  if(!Array.isArray(filter)) {
   filter = [filter.toString()];
  }
  const newObj = {};
  for(i in obj) {
    if(!filter.includes(i)) {
      newObj[i] = obj[i];
    }
  }
  return newObj;
}

i nazwij to tak:

filteredObject(myData, ['item2']); //{item1: { key: 'sdfd', value:'sdfd' }, item3: { key: 'sdfd', value:'sdfd' }}

1

Ta funkcja będzie filtrować obiekt na podstawie listy kluczy, co jest bardziej wydajne niż poprzednia odpowiedź, ponieważ nie musi on używać Array.filter przed wywołaniem redukcji. więc jego O (n) w przeciwieństwie do O (n + filtrowane)

function filterObjectByKeys (object, keys) {
  return Object.keys(object).reduce((accum, key) => {
    if (keys.includes(key)) {
      return { ...accum, [key]: object[key] }
    } else {
      return accum
    }
  }, {})
}

1

Podczas pętli nic nie zwraca, gdy napotkamy określone właściwości / klucze, i kontynuuj z resztą:

const loop = product =>
Object.keys(product).map(key => {
    if (key === "_id" || key === "__v") {
        return; 
    }
    return (
        <ul className="list-group">
            <li>
                {product[key]}
                <span>
                    {key}
                </span>
            </li>
        </ul>
    );
});

1

Dziwi mnie, że nikt jeszcze tego nie sugerował. Jest bardzo czysty i bardzo precyzyjnie określa, które klucze chcesz zachować.

const unfilteredObj = {a: ..., b:..., c:..., x:..., y:...}

const filterObject = ({a,b,c}) => ({a,b,c})
const filteredObject = filterObject(unfilteredObject)

Lub jeśli chcesz mieć brudną wkładkę:

const unfilteredObj = {a: ..., b:..., c:..., x:..., y:...}

const filteredObject = (({a,b,c})=>({a,b,c}))(unfilteredObject);

Ta metoda doda klucz nieobecny w oryginalnej tablicy. Może to stanowić problem lub nie, należy o tym przynajmniej wspomnieć.
ponchietto

0

Innym podejściem byłoby użycie Array.prototype.forEach()jako

const raw = {
  item1: {
    key: 'sdfd',
    value: 'sdfd'
  },
  item2: {
    key: 'sdfd',
    value: 'sdfd'
  },
  item3: {
    key: 'sdfd',
    value: 'sdfd'
  }
};

const allowed = ['item1', 'item3', 'lll'];

var finalObj = {};
allowed.forEach(allowedVal => {
  if (raw[allowedVal])
    finalObj[allowedVal] = raw[allowedVal]
})
console.log(finalObj)

Zawiera wartości tylko tych kluczy, które są dostępne w surowych danych, co zapobiega dodawaniu niepotrzebnych danych.


0

To byłoby moje rozwiązanie:

const filterObject = (obj, condition) => {
    const filteredObj = {};
    Object.keys(obj).map(key => {
      if (condition(key)) {
        dataFiltered[key] = obj[key];
      }
    });
  return filteredObj;
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.