Search code examples
typescriptconditional-types

Transforming an array to an object with key from common property in elements of array


I have an object similar to this:

const v = [
  { pointer: "/name", name: "N1" },
  { pointer: "/name", name: "N2" },
  { pointer: "/zip", name: "Z1" }
] as const

and I'd like to generate objects like this:

const m: M = {
  "/name": [{ pointer: "/name", name: "N2" }] // can also contain N1, but not Z1
}

Is it possible to create a type for this? My attempt should better explain what I'm trying to do:

type V = typeof v;
type T = V[number]

type M = {
  [K in T["pointer"]]: Array<T extends {pointer: K} ? T : never>;
}

codesandbox: https://codesandbox.io/s/56564


Solution

  • You're close, but the problem is that your T is a concrete type and not a generic type parameter, and thus T extends { pointer: K } ? T : never does not distributive over unions. So T extends { pointer: K } will just be false for each choice of K, and you get never[] instead of what you're looking for.

    The easiest way to get distributive conditional types back is to use a type alias of the form type Something<X> = X extends { pointer: K } ? X : never and then plug in Something<T>. Luckily, the built-in Extract<T, U> utility type already works this way. So we can write this:

    type M = { [K in T["pointer"]]?: Array<Extract<T, { pointer: K }>> };
    

    which evaluates to:

    /* 
    type M = {
        "/name"?: ({
            readonly pointer: "/name";
            readonly name: "N1";
        } | {
            readonly pointer: "/name";
            readonly name: "N2";
        })[];
        "/zip"?: {
            readonly pointer: "/zip";
            readonly name: "Z1";
        }[];
    }
    */
    

    Which is closer to what you want, I think. Note that I made the props optional, since your m constant doesn't have all the props:

    const m: M = {
        "/name": [{ pointer: "/name", name: "N2" }]
    }; // okay
    

    Okay, hope that helps; good luck!

    Playground link to code