A versatile method to organize a multi-dimensional array of items

I need help sorting a nested array using a generic function. The sorting should be based on the values of the items within the nested array.

Here is an example of 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'}
        ]
    },  
];

To achieve this sorting, I want to use my sort function as follows:

sortNestedArrays(persons, 'childs', 'name');

This means sorting the nested array 'childs' by the property 'name'.

My current approach

I have been struggling with the correct syntax for this function for quite some time 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 pointing to elements that lead to the value of the nested array.
 * @param propertyName Property 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 where I currently stand. By using KeysMatching, I was able to ensure that the argument keyOfSubArray only accepts keys that are arrays, allowing safe access to the nested array.

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. Yet, the compiler produces the error message: Property 'forEach' does not exist on type 'A[K]'.

const nestedArray = member[keyOfSubArray];
nestedArray.forEach((nestedMember) => {

});

My questions at this point

1) Why doesn't the compiler recognize that member[keyOfSubArray] must be an array and how can I resolve this issue?

2) Next, I need to define the argument propertyName, which must be a key within the items of the subarray (keyof Child). How can I properly define this as a generic within the function?

The Playground for Experimentation

Typescript Playground

Answer №1

Expanding on the response from Tobias:

type GetElementFromList<E extends any[]> = E extends (infer Item)[] ? Item : never;

/**
 * @param list Main array.
 * @param keyOfSubList Key of the elements in 'list' that point to the values of the nested array.
 * @param sortProperty Key by which the nested array is sorted.
 */
function sortNestedLists<
  L extends Record<P, S[]>,
  P extends MatchKeys<L, S[]>,
  S = GetElementFromList<L[P]>,
>(list: L[], keyOfSubList: P, sortProperty: keyof S){}

sortNestedLists(individuals, "kids", "identification")

In this instance, you will receive suggestions for keys for your last parameter because we specifically define that it should be a key within the combination of L[P] (your previous parameters), which equates to Child[]. This is accomplished by using the GetElementFromList type to extract keys from Child, rather than Child[].

Test it out in the playground.

Answer №2

In response to your initial inquiry: "Why doesn't the compiler recognize that member[keyOfSubArray] must be an array?". The reason being is that there is no inherent assumption for it to do so. You have explicitly defined the array parameter passed to the function as type A, without specifying any further constraints on this generic type.

To address this, you need to provide additional information to constrain A.

function sortNestedArrays<
  A extends Record<K, unknown[]>, 
  K extends KeysMatching<A, unknown[]>
>(array: A[], keyOfSubArray: K, propertyName: ???){}

This way, the compiler will understand that A must possess a property K which is of an array type.


The second issue follows a similar pattern. It is important to inform the compiler about the significance of the array type inside A by introducing 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 utilize P to represent the element type within the array in A, as well as for the propertyName parameter.

Playground


For conciseness, we can simplify the code:

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 adjustment will also resolve the auto-completion problem.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to pass dynamic values to a service constructor from a component?

After days of attempting to grasp 'the Angular paradigm', I still find myself struggling to understand something about services that are not singletons. It seems impossible for me to pass a runtime-determined value to a service constructor, as I ...

Decoding the HTML5 <video> tag using the html-react-parser library

I'm working on a NextJS V13 app where I need to display HTML content fetched from a CMS. Instead of using dangerouslySetHtml, I opted for the html-react-parser package to parse the HTML and replace certain embedded tags like <a>, <img>, an ...

Surprising Discovery: TypeScript - node_modules Found in Unusual Directory

Is there a way to make TypeScript imports function properly even if the node_modules directory is not directly in the tree? How can I prevent TypeScript from throwing errors when importing something like rxjs from external/node_modules. For Example: Dir ...

Utilizing ternary operators in Angular 6 tables

I need to dynamically display certain amounts based on the comparison of two interest values. Here is the logic: <td *ngIf="subTable.flexitaxMaxPaymentDate"> subTable.flexitaxMaxInterest > subTable.IRDInterest ? {{subTable.maxAmou ...

Creating, editing, and deleting data in Ng2 smart table is a seamless process that can greatly enhance

While working on my Angular 2 project, I utilized [ng2 smart table]. My goal was to send an API request using the http.post() method. However, upon clicking the button to confirm the data, I encountered the following error in the console: ERROR TypeErro ...

How can I apply unique "compilerOptions" settings to a specific file in tsconfig.json file?

Can I apply specific tsconfig options to just one file? Here is my current tsconfig.json: { ... "compilerOptions": { ... "keyofStringsOnly": false, "resolveJsonModule": true, "esModuleInterop": t ...

A guide on exporting table data to PDF and enabling printing features in Angular 7

Can anyone provide guidance on how to export my dynamic table data into Excel, PDF, and for printing using the appropriate Angular Material components and npm plugins? I have successfully exported the data as an Excel file, but am struggling with exporti ...

Scrolling with React Event

I am attempting to create a scrollbar that only appears when I scroll within a particular area using React. I am utilizing debounce and useState in my implementation. The issue: When I reach the end of the scroll, the event continues to repeat indefinitel ...

What is the correct way to initialize and assign an observable in Angular using AngularFire2?

Currently utilizing Angular 6 along with Rxjs 6. A certain piece of code continuously throws undefined at the ListFormsComponent, until it finally displays the data once the Observable is assigned by calling the getForms() method. The execution of getForm ...

Develop a versatile factory using Typescript

For my current project, I am developing a small model system. I want to allow users of the library to define their own model for the API. When querying the server, the API should return instances of the user's model. // Library Code interface Instanc ...

Differences between RxJs Observable<string> and Observable<string[]>

I'm struggling to grasp the concept of RxJS Observables, even though I have utilized the observable pattern in various scenarios in my life. Below is a snippet of code that showcases my confusion: const observable: Observable<Response> = cr ...

Guide to summing the values in an input box with TypeScript

https://i.stack.imgur.com/ezzVQ.png I am trying to calculate the total value of apple, orange, and mango and display it. Below is the code I have attempted: <div class="row col-12 " ngModelGroup="cntMap"> <div class="form-group col-6"> ...

The compatibility issue between Bootstrap and Angular 2 is causing some challenges

Hey there, I recently enrolled in an Angular 2 course on udemy and everything was running smoothly until I encountered an issue while trying to install bootstrap. I followed the installation steps diligently, but whenever I attempt to add any bootstrap el ...

Guide to upgrading ag-grid-community from 20.1.0 to 24.1.0

I'm currently encountering some errors that I can't seem to find in the AgGrid documentation. These attributes are not mentioned anywhere, not even in the Change Log. First off, these compilation errors are popping up: ERROR in : Can't bind ...

Nestjs: Accessing the request or context using a Decorator

In my current project using NestJS, I am attempting to make the executionContext accessible in a logger for the purpose of filtering logs by request. Each injectable has its own instance of a logger, and I want to maintain this setup (where the scope of t ...

Template URI parameters are being used in a router call

Utilizing the useRouter hook in my current project. Incorporating templated pages throughout the application. Implementing a useEffect hook that responds to changes in the router and makes an API call. Attempting to forward the entire URL to /api/${path ...

Is there a way for me to simultaneously run a typescript watch and start the server?

While working on my project in nodejs, I encountered an issue when trying to code and test APIs. It required running two separate consoles - one for executing typescript watch and another for running the server. Feeling frustrated by this process, I came ...

Issue TS1259: The module "".../node_modules/@types/bn.js/index"" can only be imported as the default using the 'esModuleInterop' flag

Currently, I am utilizing Hiro Stack.js which I obtained from the following link: https://github.com/hirosystems/stacks.js/tree/master/packages/transaction. For additional information, please refer to . Even when attempting to compile a fully commented out ...

Align item in center of remaining space within container using Material-UI React

I am relatively new to MUI and styling HTML components, and I have a query. I'm currently utilizing the Grid feature in my React project. My goal is to achieve something similar to this (image edited in Paint, alignment may not be accurate): https://i ...

I am currently struggling with a Typescript issue that I have consulted with several individuals about. While many have found a solution by upgrading their version, unfortunately, it

Error message located in D:/.../../node_modules/@reduxjs/toolkit/dist/configureStore.d.ts TypeScript error in D:/.../.../node_modules/@reduxjs/toolkit/dist/configureStore.d.ts(1,13): Expecting '=', TS1005 1 | import type { Reducer, ReducersMapO ...