Обновить, если существует или добавить новый элемент в массив объектов - элегантный способ в javascript + lodash

Поэтому у меня есть массив таких объектов:

var arr = [
  {uid: 1, name: "bla", description: "cucu"},
  {uid: 2, name: "smth else", description: "cucarecu"},
]

uid - уникальный идентификатор объекта в этом массиве. Я ищу элегантный способ изменения объекта, если у нас есть объект с данным uid, или добавлен новый элемент, если представленный uid не существует в массиве. Я полагаю, что функция должна вести себя так, как в консоли js:

> addOrReplace(arr, {uid: 1, name: 'changed name', description: "changed description"})
> arr
[
  {uid: 1, name: "bla", description: "cucu"},
  {uid: 2, name: "smth else", description: "cucarecu"},
]
> addOrReplace(arr, {uid: 3, name: 'new element name name', description: "cocoroco"})
> arr
[
  {uid: 1, name: "bla", description: "cucu"},
  {uid: 2, name: "smth else", description: "cucarecu"},
  {uid: 3, name: 'new element name name', description: "cocoroco"}
]

Мой нынешний путь не кажется очень элегантным и функциональным:

function addOrReplace (arr, object) {
  var index = _.findIndex(arr, {'uid' : object.uid});
  if (-1 === index) {
    arr.push(object);
  } else {
    arr[index] = object;
  }
} 

Я использую lodash, поэтому я думал о чем-то вроде измененного _.union с пользовательской проверкой равенства.

Ответы

Ответ 1

Вы можете использовать объект вместо массива:

var hash = {
  '1': {uid: 1, name: "bla", description: "cucu"},
  '2': {uid: 2, name: "smth else", description: "cucarecu"}
};

Ключи - это uids. Теперь ваша функция addOrReplace проста:

function addOrReplace(hash, object) {
    hash[object.uid] = object;
}

ОБНОВИТЬ

Также возможно использовать объект в качестве индекса в дополнение к массиву.
Таким образом, вы получили быстрый поиск, а также рабочий массив:

var arr = [],
    arrIndex = {};

addOrReplace({uid: 1, name: "bla", description: "cucu"});
addOrReplace({uid: 2, name: "smth else", description: "cucarecu"});
addOrReplace({uid: 1, name: "bli", description: "cici"});

function addOrReplace(object) {
    var index = arrIndex[object.uid];
    if(index === undefined) {
        index = arr.length;
        arrIndex[object.uid] = index;
    }
    arr[index] = object;
}

Взгляните на jsfiddle-demo (объектно-ориентированное решение, которое вы найдете здесь)

Ответ 2

Может быть

_.mixin({
    mergeById: function mergeById(arr, obj, idProp) {
        var index = _.findIndex(arr, function (elem) {
            // double check, since undefined === undefined
            return typeof elem[idProp] !== "undefined" && elem[idProp] === obj[idProp];
        });

        if (index > -1) {
            arr[index] = obj; 
        } else {
            arr.push(obj);
        }

        return arr;
    }
});

а также

var elem = {uid: 3, name: 'new element name name', description: "cocoroco"};

_.mergeById(arr, elem, "uid");

Ответ 3

Старый пост, но почему бы не использовать функцию фильтра?

// If you find the index of an existing uid, save its index then delete it
//      --- after the filter add the new object.
function addOrReplace( argh, obj ) {
  var index = -1;
  argh.filter((el, pos) => {
    if( el.uid == obj.uid )
      delete argh[index = pos];
    return true;
  });

  // put in place, or append to list
  if( index == -1 ) 
    argh.push(obj);
  else 
    argh[index] = obj;
}

Вот jsfiddle, показывающий, как это работает.

Ответ 4

В вашем первом подходе нет необходимости в Lodash благодаря findIndex():

function addOrReplace(array, item) { // (1)
  const i = array.findIndex(_item => _item.id === item.id);
  if (i > -1) array[i] = item; // (2)
  else array.push(item);
}

Пример:

const array = [
  {id: 0, name: 'Apple', description: 'fruit'},
  {id: 1, name: 'Banana', description: 'fruit'},
  {id: 2, name: 'Tomato', description: 'vegetable'}
];

addOrReplace(array, {id: 2, name: 'Tomato', description: 'fruit'})
console.log(array);
/* =>
[
  {id: 0, name: 'Apple', description: 'fruit'},
  {id: 1, name: 'Banana', description: 'fruit'},
  {id: 2, name: 'Tomato', description: 'fruit'}
]
*/

addOrReplace(array, {id: 3, name: 'Cucumber', description: 'vegetable'})
console.log(array);
/* =>
[
  {id: 0, name: 'Apple', description: 'fruit'},
  {id: 1, name: 'Banana', description: 'fruit'},
  {id: 2, name: 'Tomato', description: 'fruit'},
  {id: 3, name: 'Cucumber', description: 'vegetable'}
]
*/

(1) другие возможные имена: addOrUpdate(), upsert(), appendOrUpdate(), insertOrUpdate()...

(2) Можно также выполнить с помощью array.splice(i, 1, item)

Обратите внимание, что этот подход "изменен" (vs "immutable"): он означает вместо возврата нового массива (без прикосновения к исходному массиву) он изменяет непосредственно исходный массив. Большую часть времени это то, что вы хотите.

Ответ 5

Как насчет того, чтобы индексы массива были такими же, как uid?, Например:

arr = [];
arr[1] = {uid: 1, name: "bla", description: "cucu"};
arr[2] = {uid: 2, name: "smth else", description: "cucarecu"};

таким образом вы могли бы просто использовать

arr[affectedId] = changedObject;