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

#javascript #arrays

Вопрос:

Итак, у меня есть массив подобных объектов:

 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:

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

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

Пример:

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

upsert(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'}
]
*/

upsert(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) другие возможные имена: addOrReplace() , addOrUpdate() , appendOrUpdate() , insertOrUpdate()

(2) также может быть сделано с array.splice(i, 1, element)

Обратите внимание, что этот подход является «изменяемым» (по сравнению с «неизменяемым»): это означает, что вместо возврата нового массива (без касания исходного массива) он изменяет непосредственно исходный массив.

Комментарии:

1. это не удается в первый раз

Ответ №2:

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

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

Ключи-это жидкости. Теперь ваша функция 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 (объектно-ориентированное решение, которое вы найдете здесь).

Комментарии:

1. да, мне тоже нравится эта структура, но некоторые библиотеки, которые я использую, ее не поддерживают. Например, угловой тип , который может пересекать только массивы.

2. Вы можете использовать эту структуру только в качестве индекса. Таким образом, у вас есть быстрый поиск и необходим дополнительный массив для таких вещей, как массив.

3. голосую за! есть 2 проблемы, мне нужны ключи в отсортированном порядке в соответствии с меткой времени, и мне нужно иметь доступ ко всем элементам в виде массива для вычисления статистических показателей

Ответ №3:

Лично мне не нравятся решения, которые изменяют исходный массив/объект, поэтому я сделал вот что:

 function addOrReplaceBy(arr = [], predicate, getItem) {
  const index = _.findIndex(arr, predicate);
  return index === -1
    ? [...arr, getItem()]
    : [
      ...arr.slice(0, index),
      getItem(arr[index]),
      ...arr.slice(index   1)
    ];
}
 

И вы бы использовали его так:

 var stuff = [
  { id: 1 },
  { id: 2 },
  { id: 3 },
  { id: 4 },
];

var foo = { id: 2, foo: "bar" };
stuff = addOrReplaceBy(
  stuff,
  { id: foo.id },
  (elem) => ({
    ...elem,
    ...foo
  })
);
 

Что я решил сделать, так это сделать его более гибким:

  1. При использовании lodash -> _.findIndex() предиката может быть несколько вещей
  2. Передав обратный getItem() вызов , вы можете решить, следует ли полностью заменить элемент или внести некоторые изменения, как я сделал в своем примере.

Примечание: это решение содержит некоторые функции ES6, такие как деструктурирование, функции стрелок и другие.

Ответ №4:

Может быть

 _.mixin({
    mergeById: function mergeById(arr, obj, idProp) {
        var index = _.findIndex(arr, function (elem) {
            // double check, since undefined === undefined
            return typeof elem[idProp] !== "undefined" amp;amp; 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");
 

Ответ №5:

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

 // 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, показывающий, как это работает.

Ответ №6:

Если вы не возражаете против порядка элементов в конце, то более аккуратный функциональный подход es6 будет следующим:

 function addOrReplace(arr, newObj){ 
 return [...arr.filter((obj) => obj.uid !== newObj.uid), {...newObj}];
}

// or shorter one line version 

const addOrReplace = (arr, newObj) => [...arr.filter((o) => o.uid !== newObj.uid), {...newObj}];
 

Если элемент существует, он будет исключен, а затем в конце будет добавлен новый элемент, в основном это замена, и если элемент не найден, в конце будет добавлен новый объект.

Таким образом, у вас будет неизменяемый список. Единственное, что нужно знать, это то, что вам нужно будет выполнить какую-то сортировку, чтобы сохранить порядок списков, если вы, например, отображаете список на экране.

Надеюсь, это кому-нибудь пригодится.

Ответ №7:

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

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

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

 arr[affectedId] = changedObject;
 

Комментарии:

1. будет хорошо работать, если у меня будет гарантия, что uuid всегда int. Иногда мне кажется, что это не так. Но даже если бы у меня была такая гарантия, мне неудобно иметь массивы с отсутствующими элементами , такими как: arr[2134], arr[2135] , когда arr[0] не существует.

2. @ganqqwerty поэтому вместо классического массива используйте ассоциативный (объект), таким образом, вы можете использовать нецелочисленные индексы и не иметь «дыр» в своем массиве