2

I have a straightforward case in TypeScript, where I have an interface with a generic type T that extends an enum E. Based on the type a config type is assigned.

enum E { "A", "B" }

type ConfigsBase = { [K in E]: object }

interface Configs extends ConfigsBase {
    [E.A]: { a: string };
    [E.B]: { b: number };
}

interface MyInterface<T extends E> {
  type: T;
  config: Configs[T];
}

The question is how I can use this interface in a manner that the generic type is implicitly inferred from the object. So that I get useful type errors when the config is wrong.

const a: ??? = {
  type: E.A,
  config: { a: 1 }, 
}
// => I want this to give a typescript error because 1 is not a string

What do I need to insert for ??? so that typescript gives an error that the config is wrong. It should be something like const a: MyInterface<look for yourself what T is>.

I found a relatively tedious solution by using an identity function which infers the type automatically:

const inferType = <T extends E>(obj: MyInterface<T>): MyInterface<T> => obj

const a = inferType({
  type: E.A,
  config: { a: 1 },
})

// => TypeScript error: Type 'number' is not assignable to type 'string' 

Is this possible to do this more elegantly with a type annotation without using a function?

2
  • Here's a playground for playing with the scenario: typescriptlang.org/play?#code/…. Commented Jan 23, 2021 at 15:36
  • Can you accept my question? I will be glad. Commented Jan 23, 2021 at 20:12

3 Answers 3

1

there is a way with infer operator. See https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-inference-in-conditional-types.

You can do this:

enum E { "A", "B" }

type ConfigsBase = { [K in E]: object }

interface Configs extends ConfigsBase {
    [E.A]: { a: number };
    [E.B]: { b: number };
}

interface MyInterface<T extends E> {
  type: T;
  config: Configs[T];
}

type InferType<obj> = obj extends MyInterface<infer R> ? MyInterface<R> : never;

const variable = {
    type: E.A,
    config: { a: 1 }
};

const inferredVariable: InferType<typeof variable> = variable;

It will be faster because you don't use functions.

Updated Playground

Sign up to request clarification or add additional context in comments.

3 Comments

Haha, thanks! That's a nice alternative to the function option.
however, in this case, error messages are a little bit worse (in case you leave an original type [E.A]: { a: string };): Type '{ type: E; config: { a: number; }; }' is not assignable to type 'never'.
Yes, this will generate never, so worse description in error will be showed. @erksch, please accept my question
1

Using the sealed class concept is probably the cleanest and most explicit approach.
It also does not require any intermediate types or functions:

enum E { "A", "B" }

interface MyTypeBase<T extends E> {
  type: T;
}

interface MyTypeA extends MyTypeBase<E.A> {
  config: { a: string };
}

interface MyTypeB extends MyTypeBase<E.B> {
  config: { b: number };
}

type MyType = MyTypeA | MyTypeB;

const a: MyType = {
  type: E.A,
  config: { a: 1 }, // <-- will give type error as expected
};

In TypeScript Playground

Comments

-1

In kotlin you would use sealed classes:

sealed class Config {
    data class ConfigA(val a: String): Config()
    data class ConfigB(val b: Int): Config()
}

val a = Config.ConfigA(1)//type error here

fun main() {
    val c : Config = a

    when (c) {// no type property needed
        is Config.ConfigA -> c.a
        is Config.ConfigB -> c.b
    }
}

1 Comment

The question is about TypeScript, not Kotlin.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.