If you're seeking a solution like the one below:
type MatchingKeys<T, V> = NonNullable<
{ [K in keyof T]: T[K] extends V ? K : never }[keyof T]
>;
where MatchingKeys<T, V>
provides the keys of T
with properties that can be assigned to V
. This involves looking up property values from a mapped type based on conditional types. Here's an example to demonstrate how it operates:
interface Bar {
x?: string;
y: number;
}
// Fetching all keys of Bar where properties are assignable to
// string | undefined... expecting "x" as output
type ExampleCase = MatchingKeys<Bar, string | undefined>;
This is executed as follows:
type Result1 = NonNullable<
{
x?: Bar["x"] extends string | undefined ? "x" : never;
y: Bar["y"] extends string | undefined ? "y" : never;
}["x" | "y"]
>;
type Result2 = NonNullable<
{
x?: string | undefined extends string | undefined ? "x" : never;
y: number extends string ? "y" : never;
}["x" | "y"]
>;
type Result3 = NonNullable<{ x?: "x"; y: never }["x" | "y"]>;
type Result4 = NonNullable<"x" | undefined | never>;
type Result5 = NonNullable<"x" | undefined>;
type Result6 = "x";
The expected result is "x"
.
Moreover, IndexableKeys
can be defined simply as:
type IndexableKeys<T> = MatchingKeys<T, keyof any>;
Your indexByProp()
function would then appear as shown below:
const indexByProp = <X extends Indexable>(
keyName: IndexableKeys<X>,
items: X[]
): Indexable<X> => {
const initial: Indexable<X> = {};
return items.reduce((index, item) => {
const keyValue = item[keyName];
index[keyValue as keyof typeof index] = item; // assertion needed here
return index;
}, initial);
};
Your test cases should work correctly:
indexByProp("value", [
{
value: 1,
label: "2",
action: () => {}
}
]); // this should pass
indexByProp("action", [
// ~~~~~ error!
// Argument of type '"action"' is not assignable to parameter of type '"value" | "label"'.
{
// this should show an error
value: 1,
label: "2",
action: () => {}
}
]);
Hopefully, this explanation will assist you. Best of luck!
Click for code sample