Desestructuración de un objeto en otro objeto existente

29 Nov 2016

Introducción

No tenía previsto escribir esta entrada, pero tengo que reconocer que después de dos artículos explicando la desestructuración en ES6 (primera y segunda parte), aún continuaba teniendo problemas con algunas fórmulas. En concreto, y como reza el título del post, con la desestructuración de un objeto en otro ya existente.

Como la solución no es precisamente intuitiva he decidido exponerla aquí (junto a varias alternativas). En la conclusión explico el porqué de esta búsqueda.

El problema

Tenemos una función que recibe un objeto como parámetro, y queremos asignar (desestructurar) sus propiedades en un segundo objeto.

Adicionalmente, y para complicarlo un poco, queremos indicar un valor por defecto a esos parámetros en caso de omisión.

La solución simple y tradicional sería la de asignar cada valor de forma secuencial (¡el código es editable!):

const fn = ( params = {} ) => {
    // Our second object
    let bar = { /* ... */ };
    
    bar.x = ( params.x || 'defaultOne' );
    bar.y = ( params.y || 'defaultTwo' );

    // Printing bar with testing purposes:
    return bar.x + ', ' + bar.y;
}

fn( { x: 'Hello', y: undefined } );

Resalto las líneas que se corresponden con este modo ‘clásico’:

bar.x = ( params.x || 'defaultOne' );
bar.y = ( params.y || 'defaultTwo' );

El uso del operador ternario dentro de cada paréntesis nos permite establecer un valor por defecto en caso de omisión.

Desestructuración

Si en lugar de la solución anterior queremos optar por la nueva desestructuración, el procedimiento sería:

( { x: bar.x = 'defaultOne', y: bar.y = 'defaultTwo' } = params );

La clave aquí está en el paréntesis que envuelve toda la línea, necesario para evitar el error de sintaxis.

Como se puede ver, el orden quizá no es el más natural: hay que indicar que la propiedad ‘x‘ del objeto existente ha de asignarse a una nueva propiedad ‘x‘ del objeto existente. En esa asignación, aprovechamos para incluir el valor por defecto en caso de omisión.

El ejemplo completo queda de este modo:

const fn = ( params = {} ) => {
    let bar = { /* ... */ };
    
    ( { x: bar.x = 'defaultOne', y: bar.y = 'defaultTwo' } = params );

    return bar.x + ', ' + bar.y;
}

fn( { x: 'Hello', y: undefined } );

Usando Object.assign

Para un uso del lenguaje algo más natural que el anterior, podemos recurrir al poco frecuente Object.assign:

El método Object.assign() se utiliza para copiar los valores de todas la propiedades propias enumerables de uno o más objetos fuente a un objeto destino.

MDN, Object.assign()

En este caso, la asignación se realiza en dos pasos: una desestructuración previa de las variables que necesitamos y a continuación la asignación en sí misma:

let { x = 'defaultOne', y = 'defaultTwo' } = params;
 
Object.assign( bar, { x, y } );

De este modo, se crean dos variables intermedias, ‘x‘ e ‘y‘, las cuales se asignan inmediatamente al objeto existente.

Implementando, tenemos:

const fn = ( params = {} ) => {   
    let bar = { /* ... */ };
    let { x = 'defaultOne', y = 'defaultTwo' } = params;
    
    Object.assign( bar, { x, y } );

    return bar.x + ', ' + bar.y;
}

fn( { x: 'Hello', y: undefined } );

Quizá es más natural, pero su lectura resulta más compleja, además de que requiere de variables innecesarias.

Usando un bucle forEach

Además de la desestructuración y la asignación, podemos recurrir a un bucle de tipo forEach para expandir nuestro objeto dado:

[ 'x', 'y' ].forEach( prop => bar[ prop ] = ( params[ prop ] || 'defaultValue' ) );

El ejemplo completo:

const fn = ( params = {} ) => {    
    let bar = { /* ... */ };
    
    [ 'x', 'y' ].forEach( prop => bar[ prop ] = ( params[ prop ] || 'defaultValue' ) );

    return bar.x + ', ' + bar.y;
}

fn( { x: 'Hello', y: undefined } );

En este caso, tampoco ganamos legibilidad. Además, como efecto colateral, perdemos la posibilidad de asignar valores por defectos diferentes para cada valor omitido. En su lugar, tenemos que recurrir a uno genérico que, dependiendo de las necesidades de la aplicación, puede no ser suficiente.

Usando un bucle ‘for…in’

Asumiendo que queremos un volcado completo del objeto origen en el destino, y no únicamente algunas de sus propiedades, podemos utilizar un bucle de tipo ‘for…in‘:

for ( let prop in params ) bar[ prop ] = ( params[ prop ] || 'defaultValue' );

De este modo, nuestro bloque quedaría:

const fn = ( params = {} ) => {
    let bar = { /* ... */ };
    
    for ( let prop in params ) bar[ prop ] = ( params[ prop ] || 'defaultValue' );

    return bar.x + ', ' + bar.y;
}

fn( { x: 'Hello', y: undefined } );

Similar al ejemplo anterior, perdemos también la posibilidad de asignar valores por defecto para cada propiedad omitida. Como ventaja, sin embargo, tendríamos una copia completa del objeto, la cual puede ser útil si la aplicación así lo requiere.

Conclusión

Con estos ejemplos hemos visto hasta cinco formas diferentes de conseguir un mismo resultado: copiar las propiedades de un objeto en otro existente. Hay muchas otras, por lo que se invita al lector a que comparta más propuestas y sugerencias en los comentarios.

Volviendo al tema en sí del artículo, puede parecer que hemos complicado demasiado algo que en un principio se resuelve de un modo sencillo y natural. Si hemos expuesto el tema, es para que sirva básicamente de apoyo para el siguiente: desestructurar un objeto en otro es una funcionalidad interesante cuando trabajamos con las nuevas clases del ES6.

Si pensamos en una Programación Orientada a Objetos (POO) clásica, una clase, para producir un objeto, debe ser instanciada. Esa clase puede recibir parámetros a través de su constructor, el cual a su vez, da visibilidad a esos parámetros expandiendo el valor contextual de this (un objeto). Ahí tenemos el escenario para nuestro ejercicio: un objeto que nos es dado (los parámetros del constructor) que debe desestructurarse en otro existente (this).

Por lo tanto, y como resumen, lo que estamos introduciendo aquí, es el trabajo moderno con clases, constructores y super constructores

Más:

Solo un marciano comentando!

  1. jrvl

    El editor de código mola. Enhorabuena.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *