Issue
I wrote a function that works like this:
arr.sort(alphabeticallyBy("name"))
With a signature of:
<T extends string>(prop: T) => (a: Partial<Record<T, string>>, b: Partial<Record<T, string>>) => number
How could I extend this such that T
could be an arbitrarily nested prop of a
and b
?
Perhaps used like:
arr.sort(alphabeticallyBy(["prop1", "prop2", "prop3"]))
//alternatively
arr.sort(alphabeticallyBy("prop1.prop2.prop3"))
Solution
I'll assume you want alphabeticallyBy()
to take rest parameter of keys, corresponding to a path to a string
property in your array elements. That is, given the following array:
declare const arr: Array<
{ prop1: { prop2: { prop3: string; prop4: number }; prop5: boolean }; prop6: Date }
>;
you want the following to succeed because arr[i].prop1.prop2.prop3
is a number
:
arr.sort(alphabeticallyBy("prop1", "prop2", "prop3")); // okay
but the following should fail because arr[1].prop1.prop2
is not a number
:
arr.sort(alphabeticallyBy("prop1", "prop2")); // error!
If so we can write a recursive utility type called DeepRecord<K, V>
where K
is a tuple of property keys, and where V
is the property value type at the path indicated by K
. Like this:
type DeepRecord<K extends PropertyKey[], V> =
K extends [infer K0 extends PropertyKey, ...infer KR extends PropertyKey[]] ?
{ [P in K0]: DeepRecord<KR, V> } : V
Essentially we're using a variadic tuple type and conditional type inference to shift a key K0
off the beginning of the K
tuple to leave the rest of it, KR
and evaluating to the equivalent to Record<K0, DeepRecord<KR, V>>
. And if the tuple is empty, the base case is just V
.
And now alphabeticallyBy()
can use that instead of Record<T, string>
(not sure why you had Partial
in there but you can add ?
to DeepRecord
if you want):
declare function alphabeticallyBy<K extends PropertyKey[]>(...keys: K):
(a: DeepRecord<K, string>, b: DeepRecord<K, string>) => number;
Let's test it out:
const s = alphabeticallyBy("prop1", "prop2", "prop3");
/* const s: (
a: { prop1: { prop2: { prop3: string; }; }; },
b: { prop1: { prop2: { prop3: string; }; }; }
) => number */
Looks good. The type of s
expects objects where a.prop1.prop2.prop3
and b.prop1.prop2.prop3
are string
s. The above arr.sort()
commands behave as desired as well.
In case it matters, this also allows you to pass in zero keys, in which case a
and b
must each be just string
, so that you can do this:
["a", "b", "c"].sort(alphabeticallyBy()) // okay
It is possible to prohibit this by making K extends [PropertyKey, ...PropertyKey[]]>
instead of K extends PropertyKey[]
, but I don't think it hurts anything to allow it.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.