Reducer
Reducer

Reputation: 760

TypeScript: Infer type of nested union type

I'm using objects in TypeScript that can potentially contain a lazy link where the data can be fetched or the data itself. That's why these properties get a union type T | string.

Now I want to write a type safe resolve function to resolve nested properties that returns the nested type.

type TypeOrString<T> = T | string;


interface A {
    propA: TypeOrString<B>;
}

interface B {
    propB: TypeOrString<any[]>;
}


function resolve<T, K1 extends keyof T, K2 extends keyof T[K1]>
(data: T | string, p1: K1, p2: K2): T[K1][K2] {
    return null;
}

let a:A;
let b:TypeOrString<any[]> = resolve(a, "propA", "propB");

//Error TS2345: Argument of type '"propB"' is not assignable 
//to parameter of type '"toString" | "valueOf"'.

But the compiler gives me an error, TS2345: Argument of type '"propB"' is not assignable to parameter of type '"toString" | "valueOf"'. Is there any chance to infer the type of propB from an instance of interface A?

Upvotes: 0

Views: 2083

Answers (1)

jcalz
jcalz

Reputation: 328618

You can get kind of what you're looking for by using recursive mapped types to model the fact that not only is something "data or a string", but that the data is itself "data or a string". Like this:

type DeepTypeOrString<T> = string | {
  [K in keyof T]: DeepTypeOrString<T[K]>
}

This mostly gives you what you want for primitives and plain object types. Arrays are another story, but that will be cleared up once the conditional types feature lands in TypeScript 2.8.

Now let's define A and B by the actual data you expect them to be as opposed to the "maybe string" version:

interface A  {
  propA: B
}
interface B {
  propB: any[]
}

And your resolve() function is similar to before, except that data is declared to be of type DeepTypeOrString<T>:

declare function resolve<T, K1 extends keyof T, K2 extends keyof T[K1]>(
  data: DeepTypeOrString<T>, p1: K1, p2: K2
): T[K1][K2];

Now when you call resolve(), you will see the inference you expect:

declare const a: DeepTypeOrString<A>;
const ret = resolve(a, 'propA', 'propB') // ret is inferred as any[]

Hope that helps; good luck!

Upvotes: 4

Related Questions