I want to sort a nested array using a generic function. It should be sorted by the values of the items from the nested array.
My array:
type Person = {
id: number,
name: string,
childs: Child[]
}
type Child = {
id: number,
name: string,
}
const persons : Person[] = [
{
id: 1, name: 'Person 1',
childs:[
{id: 1, name: 'Child 1'},
{id: 2, name: 'Child 2'}
]
},
{
id: 2, name: 'Person 2',
childs:[
{id: 1, name: 'Child 1'},
]
},
{
id: 3, name: 'Person 3',
childs:[
{id: 1, name: 'Child 1'},
{id: 2, name: 'Child 2'}
{id: 3, name: 'Child 3'}
]
},
];
As a result, I want to call my sort function like this:
sortNestedArrays(persons, 'childs', 'name');
That means: Sort the nested array 'childs' by the property 'name'.
My approach
I've been struggling with the correct syntax for this function for hours now.
type ArrayElement<ArrayType extends readonly unknown[]> =
ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
type KeysMatching<T, V> = {[K in keyof T]-?: T[K] extends V ? K : never}[keyof T];
/**
* @param array Main array.
* @param keyOfSubArray Key of the elements in 'array' that leads to the value of the nested array.
* @param propertyName Key by which the nested array is sorted.
*/
function sortNestedArrays<A, K extends KeysMatching<A, unknown[]>>(array: A[], keyOfSubArray: K, propertyName: ???){
array.forEach((member) => {
const nestedArray = member[keyOfSubArray];
nestedArray.forEach((nestedMember) => {
});
});
}
This is my result so far. With KeysMatching
I could achieve that the argument keyOfSubArray
accepts only keys that are of type array, so that the nested array can be accessed safely.
However, Typescript does not understand the following lines. Since keyOfSubArray
can only be a key that points to an array, it follows that member[keyOfSubArray
must also be an array. However, the compiler throws the following error message:
Property 'forEach' does not exist on type 'A[K]'.
const nestedArray = member[keyOfSubArray];
nestedArray.forEach((nestedMember) => {
});
My questions
1st) Why doesn't the compiler recognize that member[keyOfSubArray]
must be an array and how can I solve this problem?
2nd) In the next step, I need to specify the argument propertyName
which must be a key of the items in the subarray (keyof Child
). How can I specify this correctly as generic in the function?
Playground
CodePudding user response:
To your first question: "Why doesn't the compiler recognize that member[keyOfSubArray]
must be an array?". Because it has no reason to assume so. You specified that the array
parameter passed to the function is of type A
. You did not further constrain this generic type to be of any particular shape, so it could be anything.
You should constrain A
with additional information.
function sortNestedArrays<
A extends Record<K, unknown[]>,
K extends KeysMatching<A, unknown[]>
>(array: A[], keyOfSubArray: K, propertyName: ???){}
Now the compiler knows that A
must have a property K
which has an array type.
The second problem is of similar nature. You should tell the compiler that the type of the array inside A
is also important by using another generic type P
.
function sortNestedArrays<
A extends Record<K, P[]>,
K extends KeysMatching<A, unknown[]>,
P
>(array: A[], keyOfSubArray: K, propertyName: keyof P){}
We will use P
as the type of the elements inside the array in A
and also for the propertyName
parameter.
We can make it slightly shorter:
function sortNestedArrays<
A extends Record<K, unknown[]>,
K extends KeysMatching<A, unknown[]>
>(array: A[], keyOfSubArray: K, propertyName: keyof A[K][number]){
array.forEach((member) => {
const nestedArray = member[keyOfSubArray];
nestedArray.forEach((nestedMember) => {
});
});
}
This will also fix the auto-completion issue.
CodePudding user response:
To build on Tobias' answer:
type ExtractFromArray<T extends any[]> = T extends (infer Type)[] ? Type : never;
/**
* @param array Main array.
* @param keyOfSubArray Key of the elements in 'array' that leads to the value of the nested array.
* @param propertyName Key by which the nested array is sorted.
*/
function sortNestedArrays<
A extends Record<K, C[]>,
K extends KeysMatching<A, C[]>,
C
>(array: A[], keyOfSubArray: K, propertyName: keyof ExtractFromArray<A[K]>){}
sortNestedArrays(persons, "childs", "id")
In this scenario you will have key suggestions for your last parameter because we explicitly specify that it must be a key of the combination of A[K]
(your previous params) which is Child[]
and then using the ExtractFromArray
type to get the keys of Child
and not Child[]
.
Test it in the playground.