existe uma função no lodash para substituir o item correspondente

134

Gostaria de saber se existe um método mais simples no lodash para substituir um item em uma coleção JavaScript? (Possível duplicado, mas eu não entendi a resposta lá :)

Eu olhei para a documentação deles, mas não consegui encontrar nada

Meu código é:

var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
// Can following code be reduced to something like _.XX(arr, {id:1}, {id:1, name: "New Name"});
_.each(arr, function(a, idx){
  if(a.id === 1){
    arr[idx] = {id:1, name: "Person New Name"};
    return false;
  }
});

_.each(arr, function(a){
  document.write(a.name);
});

Atualização: o objeto que estou tentando substituir possui muitas propriedades, como

{id: 1, Prop1: ..., Prop2: ... e assim por diante}

Solução:

Graças ao dfsq, mas encontrei uma solução adequada no lodash que parece funcionar bem e é bem legal e eu a coloquei em um mixin também, pois tenho esse requisito em muitos lugares. JSBin

var update = function(arr, key, newval) {
  var match = _.find(arr, key);
  if(match)
    _.merge(match, newval);
  else
    arr.push(newval);    
};

_.mixin({ '$update': update });

var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];

_.$update(arr, {id:1}, {id:1, name: "New Val"});


document.write(JSON.stringify(arr));

Solução mais rápida Como apontado por @dfsq, seguir é muito mais rápido

var upsert = function (arr, key, newval) {
    var match = _.find(arr, key);
    if(match){
        var index = _.indexOf(arr, _.find(arr, key));
        arr.splice(index, 1, newval);
    } else {
        arr.push(newval);
    }
};
Vishal Seth
fonte
7
Eu acho que você pode usar match como o segundo parâmetro para _.indexOf na linha 4 da sua "Solução mais rápida" também, não há necessidade de recalcular esse valor lá, o que deve tornar as coisas um pouco mais rápidas.
Davertron
2
Ainda mais rápido: use _.findIndexpara combinar.
Julian K
1
Apenas para expandir o que @JulianK e @davertron disseram, usar em _.findIndexvez de _.finddeixará cair o segundo _.finde o _.indexOf. Você é a iteração através da matriz 3 vezes quando tudo que você precisa é de 1.
Justin Morgan

Respostas:

191

No seu caso, tudo o que você precisa fazer é encontrar o objeto na matriz e usar o Array.prototype.splice()método, leia mais detalhes aqui :

var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];

// Find item index using _.findIndex (thanks @AJ Richardson for comment)
var index = _.findIndex(arr, {id: 1});

// Replace item at index using native splice
arr.splice(index, 1, {id: 100, name: 'New object.'});

// "console.log" result
document.write(JSON.stringify( arr ));
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>

dfsq
fonte
1
Bem, sua solução vai custar mais em termos de desempenho, pois indexOfserá muito rápida (ela usará os navegadores nativos Array.prototype.indexOf). De qualquer forma, fico feliz em encontrar uma solução que funcione para você.
dfsq
14
Por que não usar _.findIndex? Então você não precisa usar _.indexOf.
AJ Richardson
35

Parece que a solução mais simples seria usar o ES6 .mapou o lodash _.map:

var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];

// lodash
var newArr = _.map(arr, function(a) {
  return a.id === 1 ? {id: 1, name: "Person New Name"} : a;
});

// ES6
var newArr = arr.map(function(a) {
  return a.id === 1 ? {id: 1, name: "Person New Name"} : a;
});

Isso tem o bom efeito de evitar a mutação da matriz original.

Spencer
fonte
8
Mas você está criando uma nova matriz cada vez ... Vale a pena notar.
Kboom 11/11
3
A única alternativa para não criar uma nova matriz é a mutação da existente. Além disso, a criação de uma nova matriz provavelmente não terá impacto em termos de desempenho. Voto por mim.
Nobita
24

[ES6] Este código funciona para mim.

let result = array.map(item => item.id === updatedItem.id ? updatedItem : item)
shebik
fonte
1. você está criando uma nova instância da matriz, portanto, não é verdade "substituindo" um item. 2. você perderá o seu updatedItemarray se não incluir um item com o mesmo id.
Evilive
esta é a solução para 'update' e não 'upsert' (a pergunta era "existe uma função no lodash para substituir o item MATCHED") e sim, ele cria uma cópia do array, portanto, não use-o se precisar trabalhar com mesma matriz (eu não fiz)
shebik 23/10/19
21
function findAndReplace(arr, find, replace) {
  let i;
  for(i=0; i < arr.length && arr[i].id != find.id; i++) {}
  i < arr.length ? arr[i] = replace : arr.push(replace);
}

Agora vamos testar o desempenho para todos os métodos:

maligno
fonte
6
Voto negativo porque ser negativo com as pessoas ("isso é algum tipo de piada") as impede de aprender. Imagine se eu terminasse com "ser uma pessoa inteligente, espero que você não seja preguiçoso com suas emoções e pense sobre isso".
Aditya MP
5
Eu não queria machucar ninguém, mas me pergunto como a solução que foi pior do que a abordagem do criador de tópico original recebeu tantos votos. O que governa as pessoas que deram seu voto a esse respeito? E fiquei desapontado ao saber que as pessoas confiam cegamente na resposta mais votada e não têm pensamento crítico.
Evilive
1
@evilive Pontos válidos, mas não vejo como esses exigem que você se depare como se todos que anteriormente forneceram respostas / votos fossem idiotas. As partes factuais desta resposta são ótimas, o resto tem o ar de um complexo de superioridade quase que contido. Isso não ajuda ninguém. Você pode facilmente expressar sua opinião sem uma resposta emocional excessiva.
Thor84no
1
Vale ressaltar, porém, que sua solução e a solução da TC são filtradas apenas por IDs. Essa é a primeira razão pela qual esses dois estão correndo mais rápido. Os outros dois estão permitindo que você passe qualquer parte do objeto necessária para a filtragem, o que pode ser mais preferível como uma função de upsert.
Aram
10

Você também pode usar o findIndex e escolher para obter o mesmo resultado:

  var arr  = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
  var data = {id: 2, name: 'Person 2 (updated)'};
  var index = _.findIndex(arr, _.pick(data, 'id'));
  if( index !== -1) {
    arr.splice(index, 1, data);
  } else {
    arr.push(data);
  }
JVitela
fonte
6

Com o passar do tempo, você deve adotar uma abordagem mais funcional na qual evite mutações nos dados e escreva pequenas funções de responsabilidade única. Com o padrão ECMAScript 6, você pode desfrutar de paradigma de programação funcional em JavaScript com as fornecidas map, filtere reducemétodos. Você não precisa de outro lodash, sublinhado ou o que mais fazer as coisas mais básicas.

Abaixo, incluí algumas soluções propostas para esse problema, a fim de mostrar como esse problema pode ser resolvido usando diferentes recursos de linguagem:

Usando o mapa ES6:

const replace = predicate => replacement => element =>
  predicate(element) ? replacement : element
 
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const predicate = element => element.id === 1
const replacement = { id: 100, name: 'New object.' }

const result = arr.map(replace (predicate) (replacement))
console.log(result)


Versão recursiva - equivalente ao mapeamento:

Requer desestruturação e propagação da matriz .

const replace = predicate => replacement =>
{
  const traverse = ([head, ...tail]) =>
    head
    ? [predicate(head) ? replacement : head, ...tail]
    : []
  return traverse
}
 
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const predicate = element => element.id === 1
const replacement = { id: 100, name: 'New object.' }

const result = replace (predicate) (replacement) (arr)
console.log(result)


Quando a ordem da matriz final não é importante, você pode usar uma estrutura de dados objectcomo HashMap . Muito útil se você já possui uma coleção com chave object- caso contrário, é necessário alterar sua representação primeiro.

Requer propagação do restante do objeto , nomes de propriedades calculadas e Object.entries .

const replace = key => ({id, ...values}) => hashMap =>
({
  ...hashMap,       //original HashMap
  [key]: undefined, //delete the replaced value
  [id]: values      //assign replacement
})

// HashMap <-> array conversion
const toHashMapById = array =>
  array.reduce(
    (acc, { id, ...values }) => 
    ({ ...acc, [id]: values })
  , {})
  
const toArrayById = hashMap =>
  Object.entries(hashMap)
  .filter( // filter out undefined values
    ([_, value]) => value 
  ) 
  .map(
    ([id, values]) => ({ id, ...values })
  )

const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const replaceKey = 1
const replacement = { id: 100, name: 'New object.' }

// Create a HashMap from the array, treating id properties as keys
const hashMap = toHashMapById(arr)
console.log(hashMap)

// Result of replacement - notice an undefined value for replaced key
const resultHashMap = replace (replaceKey) (replacement) (hashMap)
console.log(resultHashMap)

// Final result of conversion from the HashMap to an array
const result = toArrayById (resultHashMap)
console.log(result)

Przemysław Zalewski
fonte
5

Se você está apenas tentando substituir uma propriedade, lodash _.finde _.setdeve ser suficiente:

var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];

_.set(_.find(arr, {id: 1}), 'name', 'New Person');
Andrei Gavrilov
fonte
1

Se o ponto de inserção do novo objeto não precisar corresponder ao índice do objeto anterior, a maneira mais simples de fazer isso com o lodash é usando _.rejecte pressionando novos valores na matriz:

var arr = [
  { id: 1, name: "Person 1" }, 
  { id: 2, name: "Person 2" }
];

arr = _.reject(arr, { id: 1 });
arr.push({ id: 1, name: "New Val" });

// result will be: [{ id: 2, name: "Person 2" }, { id: 1, name: "New Val" }]

Se você tiver vários valores que deseja substituir em uma passagem, faça o seguinte (gravado no formato não-ES6):

var arr = [
  { id: 1, name: "Person 1" }, 
  { id: 2, name: "Person 2" }, 
  { id: 3, name: "Person 3" }
];

idsToReplace = [2, 3];
arr = _.reject(arr, function(o) { return idsToReplace.indexOf(o.id) > -1; });
arr.push({ id: 3, name: "New Person 3" });
arr.push({ id: 2, name: "New Person 2" });


// result will be: [{ id: 1, name: "Person 1" }, { id: 3, name: "New Person 3" }, { id: 2, name: "New Person 2" }]
richt
fonte
este método muda a ordenação matriz
Sospedra
1

Usando a função lodash unionWith, você pode realizar um upsert simples em um objeto. A documentação afirma que, se houver uma correspondência, ela usará a primeira matriz. Coloque seu objeto atualizado em [] (array) e coloque-o como o primeiro array da função union. Simplesmente especifique sua lógica de correspondência e, se encontrada, a substituirá e, se não, a adicionará

Exemplo:

let contacts = [
     {type: 'email', desc: 'work', primary: true, value: 'email prim'}, 
     {type: 'phone', desc: 'cell', primary: true, value:'phone prim'},
     {type: 'phone', desc: 'cell', primary: false,value:'phone secondary'},
     {type: 'email', desc: 'cell', primary: false,value:'email secondary'}
]

// Update contacts because found a match
_.unionWith([{type: 'email', desc: 'work', primary: true, value: 'email updated'}], contacts, (l, r) => l.type == r.type && l.primary == r.primary)

// Add to contacts - no match found
_.unionWith([{type: 'fax', desc: 'work', primary: true, value: 'fax added'}], contacts, (l, r) => l.type == r.type && l.primary == r.primary)
Jeffrey
fonte
1

Variante não é ruim também)

var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];

var id = 1; //id to find

arr[_.find(arr, {id: id})].name = 'New Person';
Ivan Pirus
fonte
1
var arr= [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
var index = _.findIndex(arr, {id: 1});
arr[index] = {id: 100, name: 'xyz'}
Amit Chhatbar
fonte
0

Se você está procurando uma maneira de alterar imutável a coleção (como eu estava quando encontrei sua pergunta), você pode dar uma olhada no immutability-helper , uma biblioteca bifurcada no utilitário React original. No seu caso, você realizaria o que mencionou através do seguinte:

var update = require('immutability-helper')
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}]
var newArray = update(arr, { 0: { name: { $set: 'New Name' } } })
//=> [{id: 1, name: "New Name"}, {id:2, name:"Person 2"}]
Aaron_H
fonte
0

Você pode fazer isso sem usar o lodash.

let arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
let newObj = {id: 1, name: "new Person"}

/*Add new prototype function on Array class*/
Array.prototype._replaceObj = function(newObj, key) {
  return this.map(obj => (obj[key] === newObj[key] ? newObj : obj));
};

/*return [{id: 1, name: "new Person"}, {id: 2, name: "Person 2"}]*/
arr._replaceObj(newObj, "id") 
Ensolarado
fonte
0

Imutável , adequado para ReactJS:

Presumir:

cosnt arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];

O item atualizado é o segundo e o nome é alterado para Special Person:

const updatedItem = {id:2, name:"Special Person"};

Dica : o lodash tem ferramentas úteis, mas agora temos alguns deles em Ecmascript6 +, então eu só usarmapfunção que é existia em amboslodasheecmascript6+:

const newArr = arr.map(item => item.id === 2 ? updatedItem : item);
AmerllicA
fonte
0

Também deparei com isso e fez isso simplesmente dessa maneira.

const persons = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
const updatedPerson = {id: 1, name: "new Person Name"}
const updatedPersons = persons.map(person => (
  person.id === updated.id
    ? updatedPerson
    : person
))

Se desejado, podemos generalizá-lo

const replaceWhere = (list, predicate, replacement) => {
  return list.map(item => predicate(item) ? replacement : item)
}

replaceWhere(persons, person => person.id === updatedPerson.id, updatedPerson)
rmmjohann
fonte