Tanto Object.assign como Object spread solo hacen una fusión superficial.
Un ejemplo del problema:
// No object nesting
const x = { a: 1 }
const y = { b: 1 }
const z = { ...x, ...y } // { a: 1, b: 1 }
El resultado es lo que esperarías. Sin embargo, si intento esto:
// Object nesting
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }
En vez de
{ a: { a: 1, b: 1 } }
usted obtiene
{ a: { b: 1 } }
x se sobrescribe por completo porque la sintaxis de propagación solo tiene un nivel de profundidad. Esto es lo mismo con Object.assign()
.
¿Hay alguna forma de hacer esto?
javascript
spread-syntax
Miguel
fuente
fuente
Respuestas:
No, no lo hace.
fuente
Sé que este es un problema un poco antiguo, pero la solución más fácil en ES2015 / ES6 que pude encontrar fue en realidad bastante simple, usando Object.assign (),
Esperemos que esto ayude:
Ejemplo de uso:
Encontrarás una versión inmutable de esto en la respuesta a continuación.
Tenga en cuenta que esto conducirá a una recursión infinita en referencias circulares. Aquí hay algunas respuestas excelentes sobre cómo detectar referencias circulares si crees que enfrentarías este problema.
fuente
Object.assign(target, { [key]: {} })
si pudiera ser simplementetarget[key] = {}
?target[key] = source[key]
lugar deObject.assign(target, { [key]: source[key] });
target
. Por ejemplo,mergeDeep({a: 3}, {a: {b: 4}})
dará como resultado unNumber
objeto aumentado , que claramente no se desea. Además,isObject
no acepta matrices, pero acepta cualquier otro tipo de objeto nativo, comoDate
, que no debe copiarse en profundidad.Puede usar la fusión de Lodash :
fuente
{ 'a': [{ 'b': 2 }, { 'c': 3 }, { 'd': 4 }, { 'e': 5 }] }
?{ 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
es correcto, porque estamos fusionando elementos de una matriz. El elemento0
deobject.a
es{b: 2}
, el elemento0
deother.a
es{c: 3}
. Cuando estos dos se fusionan porque tienen el mismo índice de matriz, el resultado es{ 'b': 2, 'c': 3 }
, que es el elemento0
en el nuevo objeto.El problema no es trivial cuando se trata de objetos host o cualquier tipo de objeto que sea más complejo que una bolsa de valores
Otra cosa a tener en cuenta: gráficos de objetos que contienen ciclos. Por lo general, no es difícil de tratar, simplemente mantenga un
Set
objeto fuente ya visitado, pero a menudo se olvida.Probablemente debería escribir una función de fusión profunda que solo espere valores primitivos y objetos simples, en la mayoría de los tipos que el algoritmo de clonación estructurada puede manejar , como fuentes de fusión. Lanza si encuentra algo que no puede manejar o simplemente asignar por referencia en lugar de una fusión profunda.
En otras palabras, no existe un algoritmo único para todos, ya sea que tenga que usar el suyo propio o buscar un método de biblioteca que cubra sus casos de uso.
fuente
Aquí hay una versión inmutable (no modifica las entradas) de la respuesta de @ Salakar. Útil si estás haciendo cosas de tipo de programación funcional.
fuente
key
como el nombre de la propiedad, el segundo hará que "clave" sea el nombre de la propiedad. Ver: es6-features.org/#ComputedPropertyNamesisObject
que no es necesario para comprobar&& item !== null
al final, ya que la línea comienza conitem &&
, ¿no?mergedDeep
la salida (creo). Por ejemplo,const target = { a: 1 }; const source = { b: { c: 2 } }; const merged = mergeDeep(target, source);
merged.b.c; // 2
source.b.c = 3;
merged.b.c; // 3
¿es esto un problema? No muta las entradas, pero cualquier futura mutación a las entradas podría mutar la salida, y viceversa con mutaciones a las entradas de mutación de salida. Sin embargo, por lo que vale, ramda'sR.merge()
tiene el mismo comportamiento.Dado que este problema aún está activo, aquí hay otro enfoque:
fuente
prev[key] = pVal.concat(...oVal);
aprev[key] = [...pVal, ...oVal].filter((element, index, array) => array.indexOf(element) === index);
reduce
que no lo hace.Sé que ya hay muchas respuestas y tantos comentarios argumentando que no funcionarán. El único consenso es que es tan complicado que nadie hizo un estándar para ello. . Sin embargo, la mayoría de las respuestas aceptadas en SO exponen "trucos simples" que son ampliamente utilizados. Por lo tanto, para todos nosotros, como yo, que no somos expertos pero queremos escribir código más seguro al comprender un poco más sobre la complejidad de JavaScript, intentaré arrojar algo de luz.
Antes de ensuciarnos las manos, déjenme aclarar 2 puntos:
Object.assign
hace.Respuestas con
for..in
oObject.keys
son engañosasHacer una copia profunda parece una práctica tan básica y común que esperamos encontrar una línea o, al menos, una victoria rápida a través de una recursión simple. No esperamos que necesitemos una biblioteca o escribir una función personalizada de 100 líneas.
Cuando leí por primera vez vez la respuesta de Salakar , realmente pensé que podría hacerlo mejor y más simple (puedes compararlo con
Object.assign
onx={a:1}, y={a:{b:1}}
). Luego leí la respuesta del 8472 y pensé ... no hay escapatoria tan fácil, mejorar las respuestas ya dadas no nos llevará lejos.Dejemos una copia profunda y recursiva a un lado al instante. Solo considere cómo (erróneamente) las personas analizan las propiedades para copiar un objeto muy simple.
Object.keys
omitirá sus propias propiedades no enumerables, sus propias propiedades con clave de símbolo y todas las propiedades del prototipo. Puede estar bien si tus objetos no tienen ninguno de esos. Pero tenga en cuenta queObject.assign
maneja sus propias propiedades enumerables con clave de símbolo. Entonces su copia personalizada perdió su floración.for..in
proporcionará propiedades de la fuente, de su prototipo y de la cadena completa del prototipo sin que lo desee (o lo sepa). Su objetivo puede terminar con demasiadas propiedades, mezclando propiedades prototipo y propiedades propias.Si estás escribiendo una función de propósito general y no se está usando
Object.getOwnPropertyDescriptors
,Object.getOwnPropertyNames
,Object.getOwnPropertySymbols
oObject.getPrototypeOf
, lo más probable que estés haciendo mal.Cosas a considerar antes de escribir su función
Primero, asegúrese de comprender qué es un objeto Javascript. En Javascript, un objeto está hecho de sus propias propiedades y un objeto prototipo (padre). El objeto prototipo a su vez está hecho de sus propias propiedades y un objeto prototipo. Y así sucesivamente, definiendo una cadena prototipo.
Una propiedad es un par de clave (
string
osymbol
) y descriptor (value
oget
/set
accesor, y atributos comoenumerable
).Finalmente, hay muchos tipos de objetos . Es posible que desee manejar de manera diferente un objeto Objeto de un objeto Fecha o una función de objeto.
Entonces, al escribir su copia profunda, debe responder al menos esas preguntas:
Para mi ejemplo, considero que solo los
object Object
s son profundos , porque otros objetos creados por otros constructores pueden no ser adecuados para una mirada en profundidad. Personalizado a partir de este SO .E hice un
options
objeto para elegir qué copiar (para fines de demostración).Función propuesta
Puedes probarlo en este plunker .
Eso se puede usar así:
fuente
Yo uso lodash:
fuente
_cloneDeep(value1).merge(value2)
Aquí está la implementación de TypeScript:
Y pruebas unitarias:
fuente
El paquete deepmerge npm parece ser la biblioteca más utilizada para resolver este problema: https://www.npmjs.com/package/deepmerge
fuente
Me gustaría presentar una alternativa ES5 bastante simple. La función recibe 2 parámetros -
target
ysource
que debe ser del tipo "objeto".Target
será el objeto resultante.Target
conserva todas sus propiedades originales, pero sus valores pueden modificarse.casos:
target
no tiene unasource
propiedad, latarget
obtiene;target
tiene unasource
propiedad ytarget
&source
no son ambos objetos (3 casos de 4),target
la propiedad de se anula;target
tiene unasource
propiedad y ambos son objetos / matrices (1 caso restante), entonces la recursión ocurre fusionando dos objetos (o concatenación de dos matrices);También considere lo siguiente :
Es predecible, admite tipos primitivos, así como matrices y objetos. Además, como podemos fusionar 2 objetos, creo que podemos fusionar más de 2 mediante la función de reducción .
Eche un vistazo a un ejemplo (y juegue con él si lo desea) :
Hay una limitación: la longitud de la pila de llamadas del navegador. Los navegadores modernos arrojarán un error en un nivel de recursión realmente profundo (piense en miles de llamadas anidadas). También es libre de tratar situaciones como matriz + objeto, etc., como desee agregando nuevas condiciones y comprobaciones de tipo.
fuente
Aquí hay otra solución ES6, funciona con objetos y matrices.
fuente
Si está usando ImmutableJS , puede usar
mergeDeep
:fuente
Si las bibliotecas npm se pueden usar como una solución, object-merge-advanced de la suya realmente permite fusionar objetos profundamente y personalizar / anular cada acción de fusión utilizando una función de devolución de llamada familiar. La idea principal es más que una fusión profunda: ¿qué sucede con el valor cuando dos claves son iguales ? Esta biblioteca se encarga de eso: cuando dos claves chocan,
object-merge-advanced
pesa los tipos, con el objetivo de retener la mayor cantidad de datos posible después de la fusión:La clave del primer argumento de entrada está marcada # 1, el segundo argumento - # 2. Dependiendo de cada tipo, se elige uno para el valor de la clave de resultado. En el diagrama, "un objeto" significa un objeto simple (no una matriz, etc.).
Cuando las teclas no chocan, todas ingresan el resultado.
Desde su fragmento de ejemplo, si solía
object-merge-advanced
fusionar su fragmento de código:Su algoritmo atraviesa recursivamente todas las claves de objetos de entrada, compara y construye y devuelve el nuevo resultado combinado.
fuente
La siguiente función realiza una copia profunda de los objetos, abarca la copia de primitivas, matrices y objetos.
fuente
Una solución simple con ES5 (sobrescribir el valor existente):
fuente
La mayoría de los ejemplos aquí parecen demasiado complejos, estoy usando uno en TypeScript que creé, creo que debería cubrir la mayoría de los casos (estoy manejando matrices como datos regulares, solo reemplazándolos).
Lo mismo en JS simple, por si acaso:
Aquí están mis casos de prueba para mostrar cómo podría usarlo
Avíseme si cree que me falta alguna funcionalidad.
fuente
Si desea tener una línea única sin requerir una gran biblioteca como lodash, le sugiero que use deepmerge . (
npm install deepmerge
)Entonces puedes hacer
Llegar
Lo bueno es que viene con tipings para TypeScript de inmediato. También permite fusionar matrices . Una verdadera solución integral es esta.
fuente
Podemos usar $ .extend (true, object1, object2) para una fusión profunda. Valor verdadero denota fusionar dos objetos de forma recursiva, modificando el primero.
$ extender (verdadero, objetivo, objeto)
fuente
jQuery.isPlainObject()
. Eso expone la complejidad de determinar si algo es o no un objeto simple, que la mayoría de las respuestas aquí pasan por alto. ¿Adivina en qué idioma está escrito jQuery?Aquí una solución simple y directa que funciona como
Object.assign
simplemente profundizar y trabajar para una matriz, sin ninguna modificaciónEjemplo
fuente
Estaba teniendo este problema al cargar un estado redux en caché. Si solo cargara el estado en caché, me encontraría con errores para la nueva versión de la aplicación con una estructura de estado actualizada.
Ya se mencionó que lodash ofrece la
merge
función que usé:fuente
Muchas respuestas usan decenas de líneas de código, o requieren agregar una nueva biblioteca al proyecto, pero si usa la recursión, esto es solo 4 líneas de código.
Manejo de matrices: la versión anterior sobrescribe los valores de matrices antiguos con otros nuevos. Si desea mantener los valores de la matriz anterior y agregar los nuevos, simplemente agregue un
else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key])
bloque sobre el estadoelse
y ya está todo listo.fuente
Aquí hay otro que acabo de escribir que admite matrices. Los concatena.
fuente
Utiliza esta función:
fuente
Ramda, que es una buena biblioteca de funciones de JavaScript, tiene mergeDeepLeft y mergeDeepRight. Cualquiera de estos funciona bastante bien para este problema. Consulte la documentación aquí: https://ramdajs.com/docs/#mergeDeepLeft
Para el ejemplo específico en cuestión podemos usar:
fuente
Prueba de unidad:
fuente
Encontré solo una solución de 2 líneas para obtener una fusión profunda en javascript. Déjame saber cómo funciona esto para ti.
El objeto temporal imprimirá {a: {b: 'd', e: 'f', x: 'y'}}
fuente
merge({x:{y:{z:1}}}, {x:{y:{w:2}}})
. Il tampoco podrá actualizar los valores existentes en obj1 si obj2 también los tiene, por ejemplo conmerge({x:{y:1}}, {x:{y:2}})
.A veces no necesita una fusión profunda, incluso si lo cree así. Por ejemplo, si tiene una configuración predeterminada con objetos anidados y desea ampliarla profundamente con su propia configuración, puede crear una clase para eso. El concepto es muy simple:
Puede convertirlo en una función (no en un constructor).
fuente
Esta es una fusión profunda y barata que utiliza el mínimo código posible. Cada fuente sobrescribe la propiedad anterior cuando existe.
fuente
Estoy usando la siguiente función corta para fusionar objetos en profundidad.
Funciona muy bien para mí.
El autor explica completamente cómo funciona aquí.
fuente