import reduce from 'lodash/collection/reduce';
import isEmpty from 'lodash/lang/isEmpty';
import curry from 'lodash/function/curry';
import assign from 'lodash/object/assign';

function resolveUpdates(updates, obj = {}) {
  return reduce(updates, (acc, value, key) => {
    let updatedValue = value;
    if (!Array.isArray(value) && typeof value === 'object') {
      updatedValue = update(value, obj[key]);
    } else if (typeof value === 'function') {
      updatedValue = value(obj[key]);
    }

    if (obj[key] !== updatedValue) {
      acc[key] = updatedValue;
    }

    return acc;
  }, {});
}

function updateArray(updates, obj) {
  const newObj = [...obj];

  return reduce(updates, (acc, value, index) => {
    acc[index] = value;
    return acc;
  }, newObj);
}

/**
 * Recursively update an object or array.
 *
 * Can update with values:
 * update({ foo: 3 }, { foo: 1, bar: 2 });
 * // => { foo: 3, bar: 2 }
 *
 * Or with a function:
 * update({ foo: x => (x + 1) }, { foo: 2 });
 * // => { foo: 3 }
 */
function update(updates, obj) {
  if (typeof updates === 'function') {
    return updates(obj);
  }

  const resolvedUpdates = resolveUpdates(updates, obj);

  if (isEmpty(resolvedUpdates)) {
    return obj;
  }

  if (Array.isArray(obj)) {
    return updateArray(resolvedUpdates, obj);
  }

  return assign({}, obj, resolvedUpdates);
}

export default curry(update);

import reduce from 'lodash/collection/reduce';
import isEmpty from 'lodash/lang/isEmpty';
import curry from 'lodash/function/curry';
import assign from 'lodash/object/assign';

function resolveUpdates(updates, obj = {}) {
  return reduce(updates, (acc, value, key) => {
    let updatedValue = value;
    if (!Array.isArray(value) && typeof value === 'object') {
      updatedValue = update(value, obj[key]);
    } else if (typeof value === 'function') {
      updatedValue = value(obj[key]);
    }

    if (obj[key] !== updatedValue) {
      acc[key] = updatedValue;
    }

    return acc;
  }, {});
}

function updateArray(updates, obj) {
  const newObj = [...obj];

  return reduce(updates, (acc, value, index) => {
    acc[index] = value;
    return acc;
  }, newObj);
}

/**
 * Recursively update an object or array.
 *
 * Can update with values:
 * update({ foo: 3 }, { foo: 1, bar: 2 });
 * // => { foo: 3, bar: 2 }
 *
 * Or with a function:
 * update({ foo: x => (x + 1) }, { foo: 2 });
 * // => { foo: 3 }
 *
 * @param {Object|Function} updates
 * @param {Object|Array}    object to update
 * @return {Object|Array}   new object with modifications
 */
function update(updates, obj) {
  if (typeof updates === 'function') {
    return updates(obj);
  }

  const resolvedUpdates = resolveUpdates(updates, obj);

  if (isEmpty(resolvedUpdates)) {
    return obj;
  }

  if (Array.isArray(obj)) {
    return updateArray(resolvedUpdates, obj);
  }

  return assign({}, obj, resolvedUpdates);
}

export default curry(update);

Add some javadoc to update