Filtra le proprietà degli oggetti in base alla chiave in ES6


266

Diciamo che ho un oggetto:

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

Voglio creare un altro oggetto filtrando l'oggetto sopra in modo da avere qualcosa di simile.

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

Sto cercando un modo pulito per ottenere questo risultato utilizzando Es6, quindi gli operatori di diffusione sono disponibili per me.


ES6 non ha operatori di diffusione degli oggetti e non ne hai bisogno qui comunque
Bergi,

1

@DanDascalescu Ma questa risposta fornisce un modo ES6 per realizzare ciò che l'OP chiede, no?
Jonathan H,

E se volessi filtrare per chiave / valore?
jmchauv,

Risposte:


503

Se hai un elenco di valori consentiti, puoi conservarli facilmente in un oggetto usando:

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

Questo utilizza:

  1. Object.keysper elencare tutte le proprietà in raw(i dati originali), quindi
  2. Array.prototype.filter per selezionare le chiavi presenti nell'elenco consentito, utilizzando
    1. Array.prototype.includes per assicurarsi che siano presenti
  3. Array.prototype.reduce per costruire un nuovo oggetto con solo le proprietà consentite.

Questo farà una copia superficiale con le proprietà consentite (ma non copierà le proprietà stesse).

Puoi anche usare l'operatore diffusione oggetto per creare una serie di oggetti senza mutarli (grazie a rjerue per averlo menzionato ):

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

A fini di curiosità, se si desidera rimuovere i campi indesiderati dai dati originali (cosa che non consiglierei di fare, poiché comporta alcune brutte mutazioni), è possibile invertire il includescontrollo in questo modo:

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

Sto includendo questo esempio per mostrare una soluzione basata sulle mutazioni, ma non suggerisco di usarla.


1
Grazie ha funzionato benissimo. Ho anche trovato un approccio usando la sintassi della decostruzione. IE: const {item1, item3} = raw const newObject = {item1, item3}
29er

2
La decostruzione funzionerà (bene), ma è puramente tempo di compilazione. Non puoi renderlo un elenco dinamico di proprietà o fornire regole complesse (ad esempio un loop può avere callback di convalida collegati).
ssube,

3
Non posso votare abbastanza! Ben fatto per usare il filtro e ridurre e non costruire un altro oggetto da un ciclo for. E fantastico che tu abbia esplicitamente separato le versioni immutabili e mutabili. +1
Sukima,

3
Avviso rapido: Array.prototype.includesnon fa parte di ES6. È stato introdotto in ECMAScript 2016 (ES7).
Vineet

3
Se vuoi fare la riduzione in modo immutabile, puoi anche sostituire il contenuto della funzione con return {... obj, [key]: raw [key]}
rjerue

93

Se stai bene usando la sintassi ES6, trovo che il modo più pulito per farlo, come notato qui e qui sia:

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

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

Ora newDatacontiene:

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

Oppure, se la chiave è memorizzata come stringa:

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

In quest'ultimo caso, [key]viene convertito in item2ma poiché stai utilizzando un constcompito, devi specificare un nome per il compito. _rappresenta un valore di lancio.

Più generalmente:

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, ...

Questo non solo riduce la tua operazione a una riga, ma non richiede nemmeno di sapere quali sono le altre chiavi (quelle che vuoi conservare).


5
" _rappresenta un valore da buttare" da dove viene? La prima volta che lo vedo
yhabib il

5
Credo che questa sia una convenzione adottata dalla comunità JS. An _è semplicemente un nome di variabile valido che può essere utilizzato in JS ma poiché è praticamente senza nome, in realtà non dovrebbe essere usato in questo modo se ti interessa comunicare l'intento. Quindi, è stato adottato come un modo per indicare una variabile che non ti interessa. Ecco ulteriori discussioni a riguardo: stackoverflow.com/questions/11406823/…
Ryan H.

2
Questo è molto più ordinato della risposta accettata, evita l'overhead della creazione di un nuovo array con Object.keys () e l'overhead dell'iterazione dell'array con filtere reduce.
ericsoco,

3
@yhabib the _non importa, è solo un nome variabile, puoi rinominarlo come preferisci
Vic

1
@Gerrat Non credo che una soluzione generalizzata sarebbe una banale linea. Per questo, vorrei usare la omitfunzione di lodash : lodash.com/docs/4.17.10#omit o una delle altre soluzioni fornite qui.
Ryan H.

42

Il modo più pulito che puoi trovare è con 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
È importante sottolineare che per questo è necessario scaricare una runtime di progetto aggiuntiva in fase di runtime.
S. Esteves,

1
_.omit (obj, ["item2"]) - lodash.omit è l'opposto di lodash.pick
Alexander Solovyev

29

Nulla che non sia stato detto prima, ma per combinare alcune risposte a una risposta ES6 generale:

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
questa è una soluzione molto più semplice e performante di altre;)
pomeh

26

Solo un'altra soluzione in una linea di Modern JS senza librerie esterne .

Stavo giocando con la funzione " Destructuring ":

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);Problema di sintassi
Awol

1
Ci sono un paio di cose condensate qui: prima cosa, c'è la dichiarazione della funzione. è una funzione freccia (prende un oggetto e restituisce un oggetto). È la stessa cosa di function (obj) {return obj;}. la seconda cosa è che ES6 consente un futuro destrutturante. Nella mia dichiarazione di funzione distruggo il mio oggetto {item1, item3}. E l'ultima cosa è che io stesso invoco la mia funzione. Ad esempio, è possibile utilizzare la funzione di auto-invocazione per gestire l'ambito. Ma qui era solo per condensare il codice. Spero sia chiaro Se mi manca qualcosa, sentiti libero di aggiungere altro.
Novy,

5
questo è l'approccio moderno preferito imho
mattdlockyer,

20

Ora puoi renderlo più breve e più semplice usando il metodo Object.fromEntries (controlla il supporto del browser):

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

leggi di più su: Object.fromEntries


1
Questo è ora il modo migliore secondo me. Molto più intuitivo di ridurre.
Damon,

10

Puoi aggiungere un generico ofilter(implementato con generico oreduce) in modo da poter facilmente filtrare gli oggetti nello stesso modo in cui puoi matrici:

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
    )

Possiamo vederlo funzionare qui -

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

Verifica i risultati nel tuo browser qui sotto -

Queste due funzioni potrebbero essere implementate in molti modi. Ho scelto di attaccare Array.prototype.reduceall'interno oreducema potresti anche scrivere tutto da zero


Mi piace la tua soluzione, ma non so quanto sia più chiara / efficiente rispetto a questa .
Jonathan H

3
Ecco un benchmark che mostra che la tua soluzione è la più veloce.
Jonathan H

In oreduce, il primo accè ombreggiato in .reduce(acc, k), e ofilteril oè in ombra - oreduceè chiamato con una variabile chiamata ocome pure - che è che?
Jarrod Mosen,

nella ofiltervariabile oè ombreggiato ma punta sempre allo stesso input var; ecco perché è ombreggiato qui, perché è lo stesso - l'accumulatore ( acc) è anche ombreggiato perché mostra più chiaramente come i dati si muovono attraverso la lambda; accnon è sempre lo stesso legame , ma rappresenta sempre lo stato persistente attuale del risultato computazionale che desideriamo restituire
Grazie

Mentre il codice funziona bene e il metodo è molto buono, mi chiedo che aiuto scrivere questo codice sia per qualcuno che ha ovviamente bisogno di aiuto con JavaScript. Sono tutto per brevità ma è quasi compatto come un codice minimizzato.
Paul G Mihai,

7

Ecco come l'ho fatto di recente:

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

Hm. Sembra che tu stia mescolando la sintassi vecchia e nuova. Object.assign == ...Potresti scrivere const dummyObj = { ...obj }e const target = { ...dummyObj }. Inoltre, quest'ultimo non è affatto necessario, poiché in seguito potresti lavorare direttamente con dummyObj.
Andy,

7

ok, che ne dici di questo one-liner

    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
La soluzione più sorprendente di tutte le risposte. +1
Gergő Horváth,

cosa succede se conosci solo le chiavi che vuoi rimuovere ... non quelle chiavi che vuoi conservare?
Jason

6

Le risposte qui sono sicuramente adatte ma sono un po 'lente perché richiedono di scorrere la whitelist per ogni proprietà nell'oggetto. La soluzione di seguito è molto più rapida per set di dati di grandi dimensioni perché scorre ciclicamente nella whitelist solo una volta:

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 sulla risposta di ssube .

Ecco una versione riutilizzabile.

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

Per chiamarlo usa

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

La cosa bella delle funzioni freccia ES6 è che non devi passare allowedcome parametro.


4

Puoi fare qualcosa del genere:

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

Funziona ma non è molto chiaro, inoltre l' withistruzione non è consigliata ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with ).


4

Una soluzione più semplice senza usare filterpuò essere raggiunta con Object.entries()invece diObject.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

Ci sono molti modi per farlo. La risposta accettata utilizza un approccio Keys-Filter-Reduce, che non è il più performante.

Invece, usare un for...inciclo per scorrere le chiavi di un oggetto o scorrere le chiavi consentite e quindi comporre un nuovo oggetto è ~ 50% più performante a .

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

un. Vedi jsPerf per i parametri di riferimento di un semplice caso d'uso. I risultati differiranno in base al browser.


3

Puoi rimuovere una chiave specifica sul tuo oggetto

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
Esistono altre risposte che forniscono la domanda del PO e sono state pubblicate qualche tempo fa. Quando pubblichi una risposta, assicurati di aggiungere una nuova soluzione o una spiegazione sostanzialmente migliore, soprattutto quando rispondi a domande precedenti.
help-info.de

2

Modo semplice! Per fare questo.

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


In realtà non stai filtrando nulla qui, stai solo distruggendo i dati forniti. Ma cosa succede quando i dati cambiano?
Idris Dopico Peña,

2

Un'altra soluzione che utilizza il Array.reducemetodo "nuovo" :

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

Dimostrazione in questo violino ...


Ma mi piace la soluzione in questa risposta che sta usando e :Object.fromEntries Array.filterArray.includes

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

Dimostrazione in questo violino ...


Wilt, il tuo secondo approccio è un duplicato esatto di questa risposta fornita lo scorso anno: stackoverflow.com/a/56081419/5522000
Art Schmidt

@ArtSchmidt Grazie per il tuo commento: ho perso quello nella lunga lista. Mi riferirò invece a quella risposta.
Wilt

1

OK, che ne dici di questo:

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

e chiamalo così:

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

1

Questa funzione filtra un oggetto in base a un elenco di chiavi, è più efficiente della risposta precedente in quanto non è necessario utilizzare Array.filter prima di chiamare il comando di riduzione. quindi la sua O (n) al contrario di O (n + filtrata)

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

1

Durante il ciclo, non restituire nulla quando vengono rilevate determinate proprietà / chiavi e continuare con il resto:

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

Sono sorpreso di come nessuno l'abbia ancora suggerito. È super pulito e molto esplicito su quali chiavi vuoi conservare.

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

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

O se vuoi una fodera sporca:

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

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

Questo metodo aggiungerà una chiave non presente nell'array originale. Potrebbe essere un problema o no, questo dovrebbe essere almeno sottolineato.
ponchietto

0

Un altro approccio sarebbe usare Array.prototype.forEach()come

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)

Include i valori delle sole chiavi disponibili nei dati non elaborati e impedisce quindi l'aggiunta di dati spazzatura.


0

Questa sarebbe la mia soluzione:

const filterObject = (obj, condition) => {
    const filteredObj = {};
    Object.keys(obj).map(key => {
      if (condition(key)) {
        dataFiltered[key] = obj[key];
      }
    });
  return filteredObj;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.