how to return object with some of its values modified lodash

Viewed 47
const fruits = {
    apple: 2,
    orange: 3,
    grape: 4,
    banana: 5
}

I would like to modify some of the values of fruits where I also have access to its current value.

Something like this

const premiumFrutis = _.doSomething(fruits, apple + 2, banana + 3)

// premiumFrutis = {
//     apple: 4,
//     orange: 3,
//     grape: 4,
//     banana: 8
// }
2 Answers

This is the job of _.assignWith or _.assignInWith. The difference is that assignIn will go through the prototype chain but when dealing with plain objects, then both will work the same.

In both cases, a customiser option can be provided which will be used when the values of the objects are combined. A simple implementation is

const combineValues = (srcValue, objValue) => 
      srcValue + objValue;

which will sum values for the same key:

const fruits = {
    apple: 2,
    orange: 3,
    grape: 4,
    banana: 5
}

const combineValues = (srcValue, objValue) => 
  srcValue + objValue;

const modification = {apple: 2, banana: 3};

const premiumFrutis = _.assignWith(fruits, modification, combineValues);
console.log(premiumFrutis);
// {
//     apple: 4,
//     orange: 3,
//     grape: 4,
//     banana: 8
// }
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

However, it is not even needed to implement this, since Lodash already provides _.add and it can be directly used:

const fruits = {
    apple: 2,
    orange: 3,
    grape: 4,
    banana: 5
}

const modification = {apple: 2, banana: 3};

const premiumFrutis = _.assignWith(fruits, modification, _.add);
console.log(premiumFrutis);
// {
//     apple: 4,
//     orange: 3,
//     grape: 4,
//     banana: 8
// }
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

This will also do subtraction if you pass negative values:

const fruits = {
    apple: 2,
    orange: 3,
    grape: 4,
    banana: 5
}

const modification = {apple: 2, banana: -1};

const premiumFrutis = _.assignWith(fruits, modification, _.add);
console.log(premiumFrutis);
// {
//     apple: 4,
//     orange: 3,
//     grape: 4,
//     banana: 4
// }
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>


The only potential problem is more modifications need to be passed than there are keys in the original objects:

const fruits = {
    apple: 2,
    orange: 3,
    grape: 4,
    banana: 5
}

const modification = {apple: 2, banana: 3, lemon: 42};

const premiumFrutis = _.assignWith(fruits, modification, _.add);
console.log(premiumFrutis);
// {
//     apple: 4,
//     orange: 3,
//     grape: 4,
//     banana: 8,
//     lemon: 42
// }
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

If no new items should be added, then _.pickBy can be used to remove any modifications that are not in the initial object:

const fruits = {
    apple: 2,
    orange: 3,
    grape: 4,
    banana: 5
}

const modification = {apple: 2, banana: 3, lemon: 42};
const onlyExisting = _.pickBy(modification, (value, key) => key in fruits);

const premiumFrutis = _.assignWith(fruits, onlyExisting, _.add);
console.log(premiumFrutis);
// {
//     apple: 4,
//     orange: 3,
//     grape: 4,
//     banana: 8
// }
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

You can create a mixin function to do this. First, clone the initial object and then reduce the update object on top of it.

_.mixin({
  /**
   * Assign incremental values to a source object for a given update object.
   * @param {object} source
   * @param {object} update
   * @return {object} updated (cloned) source object
   */
  doSomething: function(source, update) {
    return _.reduce(update, function(result, value, key) {
      return _.assign(result, { [key]: source[key] + value });
    }, _.clone(source));
  }
});

const fruits = {
  apple: 2,
  orange: 3,
  grape: 4,
  banana: 5
};

const premiumFruits = _.doSomething(fruits, {
  apple:  2, // increment apple by 2
  banana: 3  // increment banana by 3
});

console.log(premiumFruits);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

Output

  "apple": 4,
  "orange": 3,
  "grape": 4,
  "banana": 8
}

ES6 Version without Lodash

const doSomething = (source, update) =>
  Object
    .entries(update)
    .reduce((result, [key, value]) =>
      ({ ...result, [key]: source[key] + value }),
      { ...source })

const fruits = {
  apple: 2,
  orange: 3,
  grape: 4,
  banana: 5,
}

const premiumFruits = doSomething(fruits, {
  apple:  2, // increment apple by 2
  banana: 3, // increment banana by 3
})

console.log(premiumFruits)


Update

Here is VLAZ's final example, but as a Lodash mixin:

Note: I replaced the key in fruits call with _.has(fruits, key) to make it more portable.

_.mixin({
  doSomething: function(source, update) {
    return _.assignWith(
      source,
      _.pickBy(update, (v, k) => _.has(source, k)),
      _.add
    );
  } 
});

const fruits = {
  apple: 2,
  orange: 3,
  grape: 4,
  banana: 5
};

const premiumFruits = _.doSomething(fruits, {
  apple:  2, // Increment apple by 2
  banana: 3, // Increment banana by 3
  lemon: 42  // Do nothing, does not exist!
});

console.log(premiumFruits);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

Related