Home > Blockchain >  Infer the type value of a property based on the type value of another property in an array of object
Infer the type value of a property based on the type value of another property in an array of object

Time:12-08

I have this type

interface Route {
    name: string,
    path: string
}

const routes = [
    {
        name: "first",
        path: "/first"
    },
    {
        name: "second",
        path: "/second"
    }
] as const

And I would like to create a function that would help me get a specific route path for the matching route.

const to = <Routes extends ReadonlyArray<Route>, CurrentRoute extends Routes[number]>(routes: Routes, name: CurrentRoute["name"]): CurrentRoute["path"] => {
    // Implementation irrelevant here
    return "" as CurrentRoute["path"]
}

const path = to(routes, "first")
// Does not work, return a union instead of a string

I would like this function to return a single string that is infered from the matching object's name so that:

  • "/first" is returned by TypeScript when calling this function with "first"
  • "/second" is returned by TypeScript when calling this function with "second"
  • ...

The implementation is irrelevant, I would like a TypeScript's type system's solution, the implementation is trivial.

Link to reproduction

CodePudding user response:

Your version doesn't work because there is no inference site for the CurrentRoute generic type argument. For better or worse, generic types cannot be inferred from indexed access types like CurrentRoute["name"]; there was an old pull request at microsoft/TypeScript#20126 which would have done this, but it never made it into the language.

To make this work you should have the name property correspond directly to a generic type parameter (let's call it N), and then use that type to compute the type corresponding to your CurrentRoute. Here's one way to do it:

const to = <R extends Route, N extends R["name"]>(
    routes: readonly R[], name: N): Extract<R, { name: N }>["path"] => {
    // Implementation irrelevant here
    return null!;
}

Because we don't care about the exact order of the elements of routes (at least not in your example), I have changed your Routes type parameter (corresponding to the entire array) to just R (corresponding to the union of members of that array). The N type parameter is constrained to R["name"], meaning the union of name property types in R.

And the return type takes R and uses the Extract<T, U> utility type to extract the union member with a name property of type N, and then indexes into that to get the path property type.

Let's make sure it works:

const path = to(routes, "first");
// const path: "/first"


const path2 = to(routes, "second");
// const path2: "/second"

Looks good!

Playground link to code

  • Related