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

Issue encountered while attempting to remove a post from my Next.js application utilizing Prisma and Zod

Currently, I'm immersed in a Next.js project where the main goal is to eliminate a post by its unique id. To carry out this task efficiently, I make use of Prisma as my ORM and Zod for data validation. The crux of the operation involves the client-sid ...

The NGINX reverse proxy fails to forward requests to an Express application

I am currently in the process of setting up a dedicated API backend for a website that operates on /mypath, but I am encountering issues with NGINX not properly proxying requests. Below is the nginx configuration located within the sites-enabled directory ...

An error encountered while trying to utilize the npm convert-units package within an Ionic 4 application

In my Ionic 4 app, I am utilizing version 2.3.4 of the npm package called convert-units. To install this package in my Ionic 4 application, I used the CLI command: npm i convert-units --save However, upon importing the library with import { convert } fro ...

Exporting declarations and different export types within a TypeScript ambient module

I am currently working on adding specific types for the config module in our application. The config module is generated dynamically from a JSON file, making it challenging to type. Since it is a node module, I am utilizing an ambient module for the typing ...

Issues with type errors in authentication wrapper for getServerSideProps

While working on implementing an auth wrapper for getServerSideProps in Next.js, I encountered some type errors within the hook and on the pages that require it. Below is the code for the wrapper along with the TypeScript error messages. It's importan ...

Guide on Implementing a Function Post-Rendering in Angular 2+

I'm looking to implement some changes in the Service file without modifying the Component.ts or directive file. Here's what I need: 1) I want to add an event listener after the service renders its content (which is generated by a third-party tool ...

What methods are available for altering state in Server Actions in NextJS 13?

Struggling to Implement State Change in NextJS 13 Server Actions I have encountered a challenge with altering state within the execution of server actions in NextJS 13. The scenario involves an actions.ts file located at the root of the app directory. Cur ...

Navigating through ionic2 with angularjs2 using for-each loops

I developed an application using IONIC-2 Beta version and I am interested in incorporating a for-each loop. Can anyone advise if it is possible to use for each in Angular-V2? Thank you. ...

You cannot assign type 'Node | null' to type 'Node' when attempting to loop through HTML elements in TypeScript

In my code, I am taking a raw Markdown string stored in 'markdownString' and using the marked.js library to convert it to HTML for display on a web browser. My goal is to extract all plain text values from the page and store them in an array of s ...

Simulate an HTTP request from a service class during a jasmine test

I initially believed that spying on services in Jasmine using the spyOn method and returning a value when the method is called was straightforward. However, it seems like my assumption may have been too simplistic? I intend to test the following action in ...

I encountered an issue while generating a crypto address on the Waves blockchain using the @waves/waves-crypto library in TypeScript

Encountering an issue with finding "crypto-js" in "@waves/waves-crypto". Despite attempts to uninstall and reinstall the module via npm and importing it using "*wavesCrypto", the error persists within the module's index.d.ts file. I am attempting to ...

How to bring in a module from another package in Angular 4

This is a fundamental query. As an individual just starting with Angular and npm, this may seem like a basic question for some. However, despite extensive research, I haven't been able to find a solution. Before embarking on a project, I want to cre ...

How to display two elements side by side within a div using React

I have an array that looks like this: const arr = [1,2,3,4,5,6,7,8,9,10] I am looking to display the elements in pairs per line within two-dimensional divs. Here is what I have in mind: This represents the main React element: render() { return <di ...

Acquire Superheroes in Journey of Champions from a REST endpoint using Angular 2

Upon completing the Angular 2 Tour of heroes tutorial, I found myself pondering how to "retrieve the heroes" using a REST API. If my API is hosted at http://localhost:7000/heroes and returns a JSON list of "mock-heroes", what steps must I take to ensure a ...

What causes the Cassandra client driver to provide more detailed information compared to cqlsh?

I'm currently utilizing the datastax nodejs-driver to retrieve information about a keyspace from cassandra. const results = await client.execute( ` DESC KEYSPACE ${keyspace} ` ); The method client.execute provides a comprehensive object containin ...

Tips for creating an API URL request with two search terms using Angular and TypeScript

I have developed a MapQuest API application that includes two input boxes - one for the "from" location and another for the "to" location for navigation. Currently, I have hardcoded the values for these locations in my app.component file, which retrieves t ...

React Routing: Unleashing the Power of Multi-Level Routing

In my quest to create a route with multiple levels (<Route path="/hello/world" element={<a>hello world</a>} />), I encountered a few issues. Here are the versions I am using: react: 18.1 react-router-dom: 6.3.0 Success with O ...

Error: The term 'makeStyles' cannot be found in the '@material-ui/core' module after installing Material-ui-pickers

Can anyone help me with using the inlineDatePicker components from Material UI pickers? I found them here: After running the npm -i command, I encountered an error during compilation: Failed to compile. ./node_modules/material-ui-pickers/dist/material-u ...

Empty nested Map in POST request

I am currently working on a springboot application with a React/Typescript frontend. I have defined two interfaces and created an object based on these interfaces. export interface Order { customer_id: number; date: Date; total: number; sp ...

How to send Multipart form data with a string payload

Many suggestions in regards to this issue recommend utilizing some form of FormData within nodejs for building a multipart form. However, I am seeking to achieve the same result without relying on the FormData library. Instead, I aim to use only request h ...