1

So, I have the following array:

const users = [
    { fname: "Samuel", lname: "Mathew", username: "sammt" },
    { fname: "Carlos", lname: "King", username: "kingg" },
] as const

And I want to extract the following type out of it:

"Samuel Mathew" | "Carlos King"

I tried the following but I don't understand why this doesn't work:

type TUserFullnames = {
  [k in number]: `${(typeof users)[k]["fname"]} ${(typeof users)[k]["lname"]}`;
}[number];

It gives me an union of all possible fname lname combinations, which is something I wasn't expecting.

Union of all possible types is understandable if I do this:

type TUserFullnames = `${(typeof users)[number]["fname"]} ${(typeof users)[number]["lname"]}`

But not sure why it yields the same result when mapping over number.

It works perfectly with an object though:

const users = {
    "sammt": { fname: "Samuel", lname: "Mathew" },
    "kingg": { fname: "Richie", lname: "Rich" },
} as const

type TUserFullnames = {
  [k in keyof typeof users]: `${(typeof users)[k]["fname"]} ${(typeof users)[k]["lname"]}`;
}[keyof typeof users];

Can someone help me this??

Developer
  • 425
  • 3
  • 15

1 Answers1

0

in maps over a union type, so when you say [k in number] there's only one member to map over i.e. number, so there's just one iteration where k is number and then your template string type becomes this:

`${(typeof users)[number]["fname"]} ${(typeof users)[number]["lname"]}`

and hence you get all possible combinations.


It's basically similar to the following index signature type:

type TUserFullnames = {
  [k: number]: `${(typeof users)[k]["fname"]} ${(typeof users)[k]["lname"]}`;
}[number];

And your example with the object works fine because keyof typeof users generates a union which is mapped as expected.


So, you need a mechanism to generate the indices of the array as union, because the following would yield the desired result:

type TUserFullnames = {
  [k: 0|1|2]: `${(typeof users)[k]["fname"]} ${(typeof users)[k]["lname"]}`;
}[0|1|2];

So, you can get the indices of a fixed length array (tuple) using the following Indices type:

type Indices<T extends readonly any[]> = Exclude<keyof T, keyof []>;

Note: keyof an array yields a union of all methods available on an array, like push, concat, map etc and if the array is of fixed length it also adds the indices into this union. So, we leverage this fact and extract just the indices from the union using Exclude.

So, now using the Indices type, you can get the desired result. Here's the complete snippet:

type Indices<T extends readonly any[]> = Exclude<keyof T, keyof []>;

const users = [
    { fname: "Samuel", lname: "Mathew", username: "sammt" },
    { fname: "Carlos", lname: "King", username: "kingg" },
] as const;

type TUserFullnames = {
    [k in Indices<typeof users>]: `${(typeof users)[k]["fname"]} ${(typeof users)[k]["lname"]}`;
}[Indices<typeof users>];



One small nit with the Indices type is that it allows variable length arrays as well, to fix that we can update the constraint for T as follows:

type Indices<T extends readonly [] | [any, ...any[]]> = Exclude<keyof T, keyof []>;
SSM
  • 2,855
  • 1
  • 3
  • 10