Issue
I'm trying to write a function that takes two objects and the name of a key that both objects contain, assuming the associated values are both arrays, and compares the length of the two arrays.
The general form of the function is this:
const compareByKeyLength = <T extends Record<string, unknown[]>, K extends keyof T>(
a: T,
b: T,
key: K,
) => {
return a[key].length < b[key].length ? 1 : -1;
};
However, I want to modify this so that T
does not necessarily extend Record<string, unknown[]>
. That is, it cannot be assumed that the T
can be indexed by any arbitrary string to get an array. Some keys on the object will be other types. What I want is to limit the allowed values for K
such that only the keys which are associated with array values can be given as input.
This is my best guess as to how to implement this, but as you can see, the compiler still doesn't accept that the values will be arrays:
// Find all keys in an object whose type is an array.
type ArrayKeys<O> = {
[K in keyof O]: O[K] extends any[] ? K : never
}[keyof O];
// Example to test if `ArrayKeys` works.
const example = {
foo: [1, 2, 3],
bar: "bar",
baz: ["one", "two", "three"],
};
// Evaluates to: "foo" | "baz"
type ExampleArrayKeys = ArrayKeys<typeof example>;
// Somehow `T[ArrayKeys<T>]` doesn't prove that the value accessed will be an array. Not sure why.
const compareByKeyLength = <T,>(
a: T,
b: T,
key: ArrayKeys<T>,
) => {
return a[key].length < b[key].length ? 1 : -1; // Error: Property 'length' does not exist on type 'T[ArrayKeys<T>]'.
};
My questions:
- What is the reason my attempt doesn't do what I want? Is it a limitation of the compiler to understand that
T[ArrayKeys<T>]
will always be an array? Or is that actually unsound and I'm just not seeing why? - Is what I'm trying to do expressible in the type system, or do I need to use runtime checks to verify that the values are actually arrays?
Solution
Maybe this is what you need:
const compareByKeyLength = <
A extends Record<Key, any[]>,
B extends Record<Key, any[]>,
Key extends keyof A & keyof B
>
(
a: A,
b: B,
key: Key,
) => {
return a[key].length < b[key].length ? 1 : -1;
};
compareByKeyLength({a: [], b: 123}, {a: [], c: 123}, "a")
I introduce the generic type Key
which is both a keyof A
and keyof B
. Then I specify that the inputs a
and b
are of the generic types A
and B
which both have the key Key
with an array type any[]
.
Answered By - Tobias S.
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.