_.assign только если свойство существует в целевом объекте

Мне нужно сделать что-то вроде _.assign, но только если целевой объект уже имеет назначенное свойство. Подумайте об этом, так как исходные объекты могут иметь некоторые свойства для внесения вклада, но также некоторые свойства, которые я не хочу смешивать.

Я никогда не использовал механизм обратного вызова _.assign, но попробовал следующее. Он "работал", но он по-прежнему привязывал свойство к объекту dest (как undefined). Я не хочу, чтобы он вообще назначался.

_.assign(options, defaults, initial, function (destVal, sourceVal) {
  return typeof destVal == 'undefined' ? undefined : sourceVal;
});

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

function softMerge (dest, source) {
    return Object.keys(dest).reduce(function (dest, key) {
      var sourceVal = source[key];

      if (!_.isUndefined(sourceVal)) {
        dest[key] = sourceVal;
      }

      return dest;
    }, dest);
}

Ответ 1

Вы можете взять только ключи от первого объекта

var firstKeys = _.keys(options);

Затем возьмем объект подмножества из второго объекта, беря только те ключи, которые существуют на первом объекте:

var newDefaults = _.pick(defaults, firstKeys);

Затем используйте этот новый объект в качестве аргумента для _.assign:

_.assign(options, newDefaults);

Или в одной строке:

_.assign(options, _.pick(defaults, _.keys(options)));

Казалось, что я работаю, когда тестировал его здесь: http://jsbin.com/yiyerosabi/1/edit?js,console

Ответ 2

Вот неизменяемая глубокая версия, я называю это "объединением, которое сохраняет форму", в TypeScript, который использует lodash:

function _mergeKeepShapeArray(dest: Array<any>, source: Array<any>) {
    if (source.length != dest.length) {
        return dest;
    }
    let ret = [];
    dest.forEach((v, i) => {
        ret[i] = _mergeKeepShape(v, source[i]);
    });
    return ret;
}

function _mergeKeepShapeObject(dest: Object, source: Object) {
    let ret = {};
    Object.keys(dest).forEach((key) => {
        let sourceValue = source[key];
        if (typeof sourceValue !== "undefined") {
            ret[key] = _mergeKeepShape(dest[key], sourceValue);
        } else {
            ret[key] = dest[key];
        }
    });
    return ret;
}

function _mergeKeepShape(dest, source) {
    // else if order matters here, because _.isObject is true for arrays also
    if (_.isArray(dest)) {
        if (!_.isArray(source)) {
            return dest;
        }
        return _mergeKeepShapeArray(dest, source);
    } else if (_.isObject(dest)) {
        if (!_.isObject(source)) {
            return dest;
        }
        return _mergeKeepShapeObject(dest, source);
    } else {
        return source;
    }
}

/**
 * Immutable merge that retains the shape of the 'existingValue'
 */
export const mergeKeepShape = <T>(existingValue: T, extendingValue): T => {
    return _mergeKeepShape(existingValue, extendingValue);
}

И простой тест, чтобы увидеть, как я вижу такое слияние:

let newObject = mergeKeepShape(
    {
        a : 5,
        // b is not here
        c : 33,
        d : {
            e : 5,
            // f is not here
            g : [1,1,1],
            h : [2,2,2],
            i : [4,4,4],
        }
    },
    {
        a : 123,
        b : 444,
        // c is not here
        d : {
            e : 321,
            f : 432,
            // g is not here
            h : [3,3,3],
            i : [1,2],
        }
    }
);
expect(newObject).toEqual({
    a : 123,
    // b is not here
    c : 33,
    d : {
        e : 321,
        // f is not here,
        g : [1,1,1],
        h : [3,3,3],
        i : [4,4,4]
    }
});

Я сам использовал в тесте "без изменений", но не видел необходимости добавлять его в этот ответ.

Настоящим я помещаю это в общественное достояние.

Ответ 3

Другим способом добиться этого является объединение _.mapObject с _.has

_.mapObject(object1, function(v, k) {
    return _.has(object2, k) ? object2[k] : v;
});

Пояснение:

  • Проведите все пары ключей/значений object1 с помощью _.mapObject
  • Используя _.has, проверьте, существует ли также имя свойства k в object2.
  • Если это так, скопируйте значение, назначенное клавише object2 k, обратно на object1, иначе просто верните существующее значение объекта1 (v).

Plunkr