TS Desafío - Merge
Published: 2022-02-04 17:18:32

En este post vamos a intentar resolver el siguiente desafío de TypeScript: [Merge Challenge](https://github.com/type-challenges/type-challenges/blob/master/questions/599-medium-merge/README.md)

> Mezclar (merge) dos tipos en un nuevo tipo. Las claves del segundo tipo sobreescribirán a las claves del primero.

Más allá de un trabalenguas lo que primero que tenemos que entender es que lo que vamos a obtener es un **objecto** que representará a un tipo por lo que en la primera aproximación simplemente lo que haremos será indicarlo. Así si `F` y `S` representan a dos objetos (tipos) podemos escribir algo como lo siguiente::

```ts
type Merge<F, S> = {}
```

De forma gráfica podemos representar a ambos tipos de la siguiente manera:

![Los tipos `F` and `S`](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/caid1ph1zlylfn4q9dee.png)

Ahora tenemos que mantener los atributos del primero y del segundo de los genéricos que estamos recibiendo. Esto se consigue utilizando el operador **[keyof](https://www.typescriptlang.org/docs/handbook/2/keyof-types.html)**. Pero, ¿cómo obtenemos los atributos de `F`? Pues simplemente escribiendo algo como lo siguiente:

```ts
[P in keyof F]
``` 

que es algo así como pedirle a TypeScript que "cree un índice que conste de todos los atributos que forman parte del genérico `F` y almacenarlos en el genérico `P` eliminando los que estén duplicados".

![`[P in keyof F]`](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c5a7j83vj2p9otoxmt42.png)

Pero, ¿cómo añadimos los atributos de `S`? Pues aquí es donde tenemos que utilizar el **[tipo de unión](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types)** para obtener todos los atributos que hay en ambos objetos. Es más, tenemos que asignarle un valor a cada uno de estos atributos, pero como en este momento no podemos saber cuál es lo que hacemos será asignarle **[never](https://www.typescriptlang.org/docs/handbook/type-compatibility.html#any-unknown-object-void-undefined-null-and-never-assignability)** porque de esta manera podemos asegurar que tenemos un tipo con un valor:

```ts
type Merge<F, S> = {
  [P in keyof F | keyof S]: never
}
```

Por lo tanto, en este punto lo que tenemos es algo como lo que se muestra en la siguiente figura:

![`[P in keyof F | keyof S]`](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/spfwrbmtplrc7t9q5gp4.png)

Ahora simplemente tenemos que determinar el valor que se asignará al atributo en cuestión. Según el enunciado del reto si el atributo pertenece al segundo genérico entonces ese será su valor mientras que si pertenece al primero será el del primero y en el caso de que no sea ninguno de los dos entonces devolveremos `nunca`.

¿Cómo podemos saber si un atributo pertenece a un objeto en TypeScript? Pues gracias al uso de la cláusula **extends (https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html#in-extends-clause)** de tal forma que, partiendo del tipo de datos que está formado por todos los atributos del segundo tipo `S`(que ya sabemos que se obtiene como `keyof S`) para saber si uno de los atributos pertenece al mismo, tendremos que escribir algo como lo siguiente:

```ts
[P in keyof F | keyof S]: P extends keyof S
```

Es decir, hemos construido el tipo de datos que es la unión de los atributos de `F` y `S` y lo hemos llamado `P`. Ahora lo que hacemos simplemente es ver si el atributo `P` pertenece al conjunto de atributos `S`.

Ahora podemos hacer uso del **[Tipo Condicional](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html)** que nos ofrece TypeScript de forma que si es un atributo de `S` lo mantendremos y en caso contrario comprobaremos si es del primer tipo. 

¿Pero qué tenemos que hacer para mantener el tipo del atributo? Pues bien, basta con acceder a él en la tipado de `S`. ¿Cómo? Muy sencillo, utilizando el operador `[]`. Es decir, si quieres acceder a un atributo de `S` escribirías algo como `S['atributo']`.  Como el atributo que pasa la condición es `P` simplemente accedemos a él de tipo `S`. En caso de que no lo sea le asignaremos `never`:

```ts
type Merge<F, S> = {
  [P in keyof F | keyof S]: P extends keyof S ? S[P] : never
}
```

Si repetimos este proceso pero esta vez viendo si el atributo está declarado en `F` para asignar su valor o en caso contrario lo que hacemos es asignarlo `never` ya que por lo que sea el atributo no forma parte de ninguno de los objetos (lo cual es imposible pero para que TypeScript no dé errores tenemos que completar el operador condicional).

Esto nos deja el siguiente resultado para el desafío:

```ts
type Merge<F, S> = {
  [P in keyof F | keyof S]: P extends keyof S
    ? S[P] 
    : P extends keyof F
      ? F[P]
      : never
}
```

Ahora si vamos a [ejecutar el resultado de nuestro challege](https://tsch.js.org/599/play) para ver si tenemos errores o no veremos que todas las pruebas mostradas pasan correctamente por lo que la solución propuesta es correcta.

![Checking the solution](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f5ldzhxj68q17vxc3umm.png)