martes, 31 de marzo de 2026

Fórmulas tipadas para manipular keys de objetos

 Quiero una función que dados varios objetos que no tienen ninguna key en común, genere un único objeto con todas las propiedades de cada objeto. Quiero que sea en Typescript con el mayor tipado posible.

Perfecto—esto es básicamente un merge de objetos disjuntos con inferencia fuerte de tipos.
La clave es lograr que el resultado sea la intersección de todos los tipos de entrada.


🧠 Objetivo

merge(
{ a: 1 },
{ b: "x" },
{ c: true }
)

👉 resultado (y tipo):

{ a: number; b: string; c: boolean }

✅ Implementación con tipado fuerte

// Convierte una unión en intersección
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends
(k: infer I) => void
? I
: never;

// Helper para mejorar legibilidad del tipo final
type Simplify<T> = { [K in keyof T]: T[K] };

export function mergeObjects<
T extends readonly Record<PropertyKey, unknown>[]
>(
...objs: T
): Simplify<UnionToIntersection<T[number]>> {
return Object.assign({}, ...objs);
}

🔥 Uso

const result = mergeObjects(
{ a: 1 },
{ b: "hola" },
{ c: true }
);

👉 Tipo inferido:

{
a: number;
b: string;
c: boolean;
}

🧠 Por qué funciona

1. T[number]

T = [{a}, {b}, {c}]
T[number] = {a} | {b} | {c}

2. UnionToIntersection

{a} | {b} | {c}
{a} & {b} & {c}

3. Resultado final

{ a } & { b } & { c }
{ a, b, c }

⚠️ Suposición importante

Esto funciona perfecto porque vos garantizás:

❗ “no tienen ninguna key en común”

Si hubiera conflicto:

{ a: number } & { a: string }

👉 anever 💥


🚀 Bonus: hacer que falle si hay keys duplicadas

Si querés máxima seguridad, se puede agregar validación de tipos para evitar colisiones (más avanzado, pero posible).


🧠 Alternativa simple (menos estricta)

function mergeLoose(...objs: object[]) {
return Object.assign({}, ...objs);
}

👉 funciona, pero:

  • ❌ pierde inferencia precisa
  • ❌ tipo: object

🧩 TL;DR

✔ Usás:

  • T[number] → unión de tipos
  • UnionToIntersection → intersección
  • Object.assign → runtime

👉 Resultado:

tipo exacto con todas las propiedades combinadas


Si querés, en el siguiente paso puedo darte:

👉 versión que detecta conflictos de keys en compile-time
👉 o una que hace merge profundo (deep merge) con tipado fuerte (esto ya es nivel avanzado heavy 😄)



Ahora quiero una función que dado un objeto y un valor, me devuelva un array con cada key del objeto cuyo valor corresponde al valor dado. Quiero que tenga un buen nivel de tipado en Typescript.

Buenísimo—esto es un clásico de “reverse lookup” tipado.
La clave es que el tipo de retorno sea solo las keys cuyo valor coincide con el tipo del valor que pasás.


🧠 Objetivo

const obj = {
a: 1,
b: "x",
c: 1,
d: true
};

keysByValue(obj, 1);
// → ["a", "c"]
// tipo: ("a" | "c")[]

✅ Implementación bien tipada

// Extrae las keys de T cuyos valores son asignables a V
type KeysMatching<T, V> = {
[K in keyof T]: T[K] extends V ? K : never
}[keyof T];

export function keysByValue<
T extends Record<PropertyKey, unknown>,
V
>(
obj: T,
value: V
): KeysMatching<T, V>[] {
const result: KeysMatching<T, V>[] = [];

for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const k = key as keyof T;

// comparación en runtime
if (obj[k] === value) {
result.push(k as KeysMatching<T, V>);
}
}
}

return result;
}

🔥 Uso con inferencia fuerte

const obj = {
a: 1,
b: "x",
c: 1,
d: true
} as const;

const r = keysByValue(obj, 1);

👉 tipo de r:

("a" | "c")[]

⚠️ IMPORTANTE (muy importante para el tipado)

Para que funcione bien necesitás:

as const

👉 si no:

const obj = {
a: 1,
b: "x"
};

TypeScript infiere:

{ a: number; b: string }

Entonces:

keysByValue(obj, 1)

👉 tipo:

("a" | "b")[]

💥 porque number podría coincidir con más cosas


🧠 Por qué funciona

1. Mapped type

[K in keyof T]: T[K] extends V ? K : never

👉 filtra keys


2. Indexación final

[keyof T]

👉 convierte en unión:

"a" | "c"

🚀 Variante más estricta (igualdad exacta)

Si querés evitar matches amplios (number vs 1):

type KeysExactlyMatching<T, V> = {
[K in keyof T]: [T[K]] extends [V]
? [V] extends [T[K]]
? K
: never
: never
}[keyof T];

🧩 TL;DR

✔ devuelve las keys cuyo valor coincide
✔ tipado fuerte usando KeysMatching
✔ mejor precisión con as const


Si querés, puedo darte una versión:

👉 que soporte comparación custom (deepEqual, por ejemplo)
👉 o que devuelva directamente un Record filtrado en lugar de un array (muy útil en pipelines)

No hay comentarios: