Combinar dos objetos sin anular

4 minutos de lectura

Tengo un objeto predeterminado como ese:

var default = {
    abc: "123",
    def: "456",
    ghi: {
       jkl: "789",
       mno: "012"
    }
};

Y tengo otro como:

var values = {
    abc: "zzz",
    ghi: {
       jkl: "yyy",
    }
};

¿Cómo puedo fusionar esos 2 objetos con el siguiente resultado (sin anular)?

var values = {
    abc: "zzz",
    def: "456",
    ghi: {
       jkl: "yyy",
       mno: "012"
    }
};

(¡No quiero cambiar el objeto predeterminado!)

Avatar de usuario de Oriol
Oriol

Para aquellos que no usan jQuery, aquí viene una solución vanilla-js.

Solución:

function extend (target) {
    for(var i=1; i<arguments.length; ++i) {
        var from = arguments[i];
        if(typeof from !== 'object') continue;
        for(var j in from) {
            if(from.hasOwnProperty(j)) {
                target[j] = typeof from[j]==='object'
                ? extend({}, target[j], from[j])
                : from[j];
            }
        }
    }
    return target;
}

Comprimido (con Compilador de cierre):

¡Solo 199 caracteres!

var extend=function e(c){for(var d=1;d<arguments.length;++d){var a=arguments[d];if("object"===typeof a)for(var b in a)a.hasOwnProperty(b)&&(c[b]="object"===typeof a[b]?e({},c[b],a[b]):a[b])}return c}

Cómo utilizar:

extend(target, obj1, obj2); // returns target

Si solo desea fusionar, use

var merged = extend({}, obj1, obj2);

Características:

  • No mira el prototipo de los objetos.
  • Ignora los no objetos.
  • Es recursivo para fusionar propiedades que son objetos.
  • Objetos referenciados en targetLas propiedades de, si se amplían, se sustituyen por otras nuevas y las originales no se modifican.
  • En el caso de los mismos nombres de propiedad, el valor fusionado será la fusión de los objetos después del último (en el orden de los argumentos) valor que no es objeto. O, si el último no es un objeto, en sí mismo.

Ejemplos:

extend({}, {a:1}, {a:2});            // {a:2}
extend({}, {a:1}, {b:2});            // {a:1, b:2}
extend({}, {a: {b:1}}, {a: {b:2}});  // {a: {b:2}}
extend({}, {a: {b:1}}, {a: {c:2}});  // {a: {b:2, c:2}}
extend({}, {a: {a:1}}, {a: {b:2}}, {a: 'whatever non object'});
    // {a: "whatever non object"}
extend({}, {a: {a:1}}, {a: {b:2}}, {a: 'whatever non object'}, {a: {c:3}},{a: {d:4}});
    // {a: {c:3, d:4}}

Advertencia:

Tenga en cuenta que si el navegador no es lo suficientemente inteligente, podría quedar atrapado en un bucle infinito:

var obj1={},
    obj2={};
obj1.me=obj1;
obj2.me=obj2;
extend({},obj1,obj2);

Si el navegador es lo suficientemente inteligente, puede arrojar un error o devolver {me: undefined}o lo que sea.

Tenga en cuenta que esta advertencia también se aplica si usa jQuery’s $.extend.

  • Un problema. Convierte matrices en objetos.

    – usuario3123032

    30 de agosto a las 14:20

avatar de usuario de adeneo
adeneo

Ahora que ES2015 es compatible con todos los navegadores modernos, el nativo Object.assign se puede usar para extender objetos

Object.assign({}, _default, values)

Objeto.asignar

Tenga en cuenta que default es una palabra clave reservada y no se puede utilizar como nombre de variable


La respuesta original, escrita en 2013:

Dado que esto está etiquetado con jQuery, podría usar $.extender para una solución simple entre navegadores

var temp = {};
$.extend(true, temp, _default, values);
values = temp;

  • Pero esto cambiará mi ‘default_array’ predeterminado y no lo quiero, porque es una variable global…

    – amplificador

    15 de diciembre de 2013 a las 1:21

  • @amp: eso se evita fácilmente usando un objeto temporal.

    – adeneo

    15 de diciembre de 2013 a las 1:29

  • esto no funciona, ghi se sobrescribirán, no se fusionarán.

    – Nate

    12 de junio de 2019 a las 4:04

  • sí, los objetos existentes se eliminan dentro de json anidado

    – Phaneendra Charyulu Kanduri

    2 de junio de 2021 a las 16:42

Además, si está satisfecho con ES6:

Object.assign({}, default, values)

  • Todavía anula si el valor existe por defecto

    –Michael Heuberger

    27 de mayo de 2021 a las 7:45

Otra forma sería simplemente ampliar el valor de forma predeterminada (en lugar de lo contrario, lo que anularía el valor predeterminado)

Object.assign(value, default) // values of default overrides value
default = value // reset default to value

La advertencia aquí es que el contenido del valor también cambia. La ventaja es que no se requiere una biblioteca y es más fácil que la solución simple anterior.

Mi versión basada en la respuesta de Oriol agrega una verificación de matrices, para que las matrices no se transformen en cosas divertidas {‘0’: …, ‘1’: …}

function extend (target) {
  for(var i=1; i<arguments.length; ++i) {
    var from = arguments[i];
    if(typeof from !== 'object') continue;
    for(var j in from) {
      if(from.hasOwnProperty(j)) {
        target[j] = typeof from[j]==='object' && !Array.isArray(from[j])
          ? extend({}, target[j], from[j])
          : from[j];
      }
    }
  }
  return target;
}

Avatar de usuario de Henric Malmberg
henric malmberg

Descubrí que la forma más fácil de hacerlo es usar mergeWith() de lodash ( https://lodash.com/docs/4.17.15#mergeWith ) que acepta una función de personalización que decide qué hacer con cada combinación de propiedades. Aquí hay uno que es recursivo:

const mergeFunction = (objValue, srcValue) => {
  if (typeof srcValue === 'object') {
    _.mergeWith(objValue, srcValue, mergeFunction)
  } else if (objValue) {
    return objValue
  } else {
    return srcValue
  }
}

¿Ha sido útil esta solución?