Update if exists or add new element to array of objects - elegant way in javascript + lodash

So I have an array of objects like that:

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

uid is unique id of the object in this array. I'm searching for the elegant way to modify the object if we have the object with the given uid, or add a new element, if the presented uid doesn't exist in the array. I imagine the function to be behave like that in js console:

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

My current way doesn't seem to be very elegant and functional:

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

I'm using lodash, so I was thinking of something like modified _.union with custom equality check.

Asked By: ganqqwerty
||

Answer #1:

You can use an object instead of an array:

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

The keys are the uids. Now your function addOrReplace is simple like this:

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

UPDATE

It's also possible to use an object as an index in addition to the array.
This way you've got fast lookups and also a working array:

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

Take a look at the jsfiddle-demo (an object-oriented solution you'll find here)

Answered By: friedi

Answer #2:

In your first approach, no need for Lodash thanks to findIndex():

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

Example:

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) other possible names: addOrReplace(), addOrUpdate(), appendOrUpdate(), insertOrUpdate()...

(2) can also be done with array.splice(i, 1, item)

Note that this approach is "mutable" (vs "immutable"): it means instead of returning a new array (without touching the original array), it modifies directly the original array.

Answered By: tanguy_k

Answer #3:

I personally do not like solutions that modify the original array/object, so this is what I did:

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

And you would use it like:

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

What I decided to do was to make it more flexible:

  1. By using lodash -> _.findIndex(), the predicate can be multiple things
  2. By passing a callback getItem(), you can decide whether to fully replace the item or do some modifications, as I did in my example.

Note: this solution contains some ES6 features such as destructuring, arrow functions, among others.

Answered By: pgarciacamou

Answer #4:

Maybe

_.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;
    }
});

and

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

_.mergeById(arr, elem, "uid");
Answered By: Tomalak

Answer #5:

Old post, but why not use the filter function?

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

Here is a jsfiddle showing how it works.

Answered By: Chris Sullivan

Answer #6:

What about having the indexes of the array same as the uid?, like:

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

that way you could just simply use

arr[affectedId] = changedObject;
Answered By: Drecker
The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 .



# More Articles