Reputation: 2245
I have a global type definition which is an object with keys (what I call a prop
) that the user has to pass as parameter to myFunction()
, and values which are used only by my code to do type checking.
Values can be of only 3 "possible types" (contained in Possible
). Each possible type has a different type definition.
I would like myFunction()
to accept as parameter only props
whose value's test
property is "possible1"
.
The generic type OnlyPossible1Props<T>
shows the way I would implement this, but it doesn't work.
First, an error is shown:
Type 'prop' cannot be used to index type 'T'. (2536)
And in addition, it returns string | number
instead of "abc" | "def"
in myFunction()
.
The closest thing I've been able to do is the piece of code I commented out. However, it loses the information of the key along the way because I don't keep it somewhere.
For an easiest way to debug, here's a link to the TypeScript Playground.
type Possible = "possible1" | "possible2" | "possible3";
type ValueBase<T extends Possible, U, V> = {
test: T,
other1: U,
other2: V
};
type Value1<T> = ValueBase<"possible1", T, T>;
type Value2<T> = ValueBase<"possible2", T, [T, T]>;
type Value3<T> = ValueBase<"possible3", T, T[]>;
type Definition = {
[prop: string]: Value1<any> | Value2<any> | Value3<any>
};
interface Example extends Definition {
"abc": {
test: "possible1",
other1: string
other2: string
},
"def": {
test: "possible1",
other1: string
other2: string
},
"ghi": {
test: "possible2",
other1: string
other2: [string, string]
},
"jkl": {
test: "possible3",
other1: string
other2: string[]
}
}
// type OnlyPossible1Props<T> = T extends object ?
// T[keyof T] extends infer A ?
// A extends { test: "possible1" }
// ? A : never : never : never;
type OnlyPossible1Props<T> = T extends object ?
keyof T extends infer prop ?
T[prop] extends infer A ?
A extends { test: "possible1" }
? prop : never : never : never : never;
function myFunction<Prop extends OnlyPossible1Props<Example>>(prop: Prop) { }
Upvotes: 0
Views: 31
Reputation: 71
The following works, but only if you remove extends Definition
from Example
:
type OnlyPossible1Props<T> = {
[K in
{ [J in keyof T]: T[J] extends { test: "possible1" } ? J : never }[keyof T]
]?: T[K]
};
This results in:
type ExampleOnlyPossible1Props = OnlyPossible1Props<Example>;
// equivalent to:
type ExampleOnlyPossible1Props = {
abc?: {
test: "possible1";
other1: string;
other2: string;
};
def?: {
test: "possible1";
other1: string;
other2: string;
};
}
(You may want to skip the optional modifier ?
).
This gives us:
myFunction({ "abc": { test: "possible1", other1: "foo", other2: "bar" } }) // OK
myFunction({ "def": { test: "possible2", other1: ["a", "b"], other2: "bcd" } }) // Error
You can see that in this TypeScript Playground.
The reason this does not work if Example extends Definition
is that the index signature on Definition
means keyof Example
evaluates to string | number
, rather than the actual keys of Example
. If extends Definition
is removed, we get keyof Example
as "abc" | "def" | "ghi" | "jkl"
.
If you want myFunction
to accept one of the possible1
props from Example
, you can instead use:
type OnlyPossible1Props<T> = {
[J in keyof T]: T[J] extends { test: "possible1" } ? T[J] : never
}[keyof T];
This gives you a union type that you can see that in this TypeScript Playground:
myFunction({ test: "possible1", other1: "foo", other2: "bar" }) // OK
myFunction({ test: "possible2", other1: ["a", "b"], other2: "bcd" }) // Error
Upvotes: 1