Ways to broaden the type signature of a Typescript comparator in order to facilitate sorting by properties nested within objects?

Here is a function that I created:

arr.sort(alphabeticallyBy("name"))

The function has the following signature:

<T extends string>(prop: T) => (a: Partial<Record<T, string>>, b: Partial<Record<T, string>>) => number

I am wondering how I can modify this to allow T to be an arbitrarily nested property of objects a and b?

Could it be utilized like this:

arr.sort(alphabeticallyBy(["prop1", "prop2", "prop3"]))
//or
arr.sort(alphabeticallyBy("prop1.prop2.prop3"))

Answer №1

Let's suppose you need the alphabeticallyBy() function to accept a rest parameter of keys, each pointing to a string property within your array elements. For example, considering the following array:

declare const arr: Array<
  { prop1: { prop2: { prop3: string; prop4: number }; prop5: boolean }; prop6: Date }
>;

You would expect this code to work because arr[i].prop1.prop2.prop3 is a number:

arr.sort(alphabeticallyBy("prop1", "prop2", "prop3")); // should be fine

However, this code should result in an error since arr[1].prop1.prop2 is not a number:

arr.sort(alphabeticallyBy("prop1", "prop2")); // error! 

If that's the case, we can implement a recursive utility type called DeepRecord<K, V>, where K represents a tuple of property keys and V is the value type at the specified path indicated by K. Check out the example below:

type DeepRecord<K extends PropertyKey[], V> =
    K extends [infer K0 extends PropertyKey, ...infer KR extends PropertyKey[]] ?
    { [P in K0]: DeepRecord<KR, V> } : V

This utilizes variadic tuple types and conditional type inference to extract the first key off the tuple and apply it recursively until reaching the base case. Now, instead of using Record<T, string>, alphabeticallyBy() can utilize DeepRecord<K, string>:

declare function alphabeticallyBy<K extends PropertyKey[]>(...keys: K):
    (a: DeepRecord<K, string>, b: DeepRecord<K, string>) => number;

Testing it out:

const s = alphabeticallyBy("prop1", "prop2", "prop3");
/* const s: (
    a: { prop1: { prop2: { prop3: string; }; }; },
    b: { prop1: { prop2: { prop3: string; }; }; }
  ) => number */

Everything seems to be working as expected. This setup also allows for zero keys to be passed, ensuring objects must contain only strings in such cases:

["a", "b", "c"].sort(alphabeticallyBy()) // okay

The implementation currently permits passing zero keys, but this behavior can be restricted if necessary by modifying the type constraint.

Playground link to code

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

Strange behavior of Lambda function in Typescript

Within a larger class, I'm working with the following code snippet: array.map(seq => this.mFunction(seq)); After compiling using the tsc command, it becomes: array.map(function (seq) { return _this.mFunction(seq); }); Everything seems fine so f ...

Angular textbox with dynamic concatenated name functionality

Hello, I'm currently working with some code that looks like this: <div *ngFor="let phone of phoneList; let phIndx = index;"> <div class="peoplePhoneTxtDiv"> <input [disabled]="phone.disabled" class="peoplePhoneTxtBox" type="text" ...

What is the best approach to creating multiple dropdowns in ant-design with unique options for each?

It seems like I may be overlooking a simple solution here. Ant-Design dropdowns utilize an array of ItemProp objects to show the options, but this restricts me to having only one list of options. const choices: MenuProps['items'] = [ { label: ...

Encountering this issue: Unable to access the property 'length' of an undefined variable

I'm currently developing an app using nuxt, vuetify 2, and typescript. Within the app, I have radio buttons (referred to as b1 and b2) and text fields (referred to as t1, t2, t3). When a user clicks on b1, it displays t1 and t3. On the other hand, w ...

The specified property cannot be found on the Window type and the globalThis typeof

I encountered an error in my Electron-React-Typescript app that reads: Property 'api' does not exist on type 'Window & typeof globalThis'. window.api.send('open-type-A-window', ''); The issue seems to be related ...

Issue TS7053 occurs when trying to access any index of the target of a React.FormEvent<HTMLFormElement>

I've been working on adapting this tutorial to React and TypeScript. Here is the code snippet I have implemented for handling the onSubmit event: const handleSignUp = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); ...

How can I use regex within a pipe to split a string in Angular 4?

I need to implement a method where I can split a string based on special characters and spaces in the given regex format, excluding "_". Example: #abc_xyz defgh // output #abc_xyz Example: #abc@xyz defgh // output #abc Example: #abc%xyz&defgh // out ...

Sending information from the parent component to the child Bootstrap Modal in Angular 6

As a newcomer to Angular 6, I am facing challenges with passing data between components. I am trying to launch a child component bootstrap modal from the parent modal and need to pass a string parameter to the child modal component. Additionally, I want t ...

Ways to avoid route change triggered by an asynchronous function

Within my Next.js application, I have a function for uploading files that includes the then and catch functions. export const uploadDocument = async (url: UploadURLs, file: File) => { const formData = new FormData(); formData.append("file" ...

Subscribing to valueChanges in reactive forms to dynamically update checkbox options

My goal is to create a select dropdown with options for bmw, audi, and opel. The user can only select one option from the dropdown, but they should also have the ability to select the options that were not chosen using checkboxes. cars = [ { id: 1, na ...

Retrieve a collection of Firestore documents based on an array of unique identifiers

I have a Firestore collection where one of the values is an array of document IDs. My goal is to retrieve all items in the collection as well as all documents stored within the array of IDs. Here is the structure of my collection: This is my code: export ...

This phrase cannot be invoked

My code seems correct for functionality, but I am encountering an error in my component that I do not know how to resolve. Can someone please help me with this issue? This expression is not callable. Not all constituents of type 'string | ((sectionNa ...

"Utilizing the `useState` function within a `Pressable

Experiencing some unusual behavior that I can't quite figure out. I have a basic form with a submit button, and as I type into the input boxes, I can see the state updating correctly. However, when I click the button, it seems to come out as reset. Th ...

Optimizing your data layer in Angular2: A Guide to Best Practices

As a newcomer to Angular2, I am diving into hands-on learning. My current project involves building multiple views with parent components, child components, and database services. After successfully creating one view, I am now gearing up to implement other ...

A step-by-step guide on customizing the background color of a Dialog in Angular Material (Version 16)

I've been attempting to modify the background color of my Angular Material Dialog by utilizing the panelClass property in the MatDialogConfig. Unfortunately, I'm encountering a partial success. I am aiming to set the background color as red (jus ...

Is there a method in TypeScript to retrieve property names from an interface resembling reflections in C#?

I am working with an interface in TypeScript/Angular that has various properties. I'm curious if there is a way to access the property names within the code. Here's an example of what my interface looks like: export interface InterfaceName ...

We could not find the requested command: nodejs-backend

As part of my latest project, I wanted to create a custom package that could streamline the initial setup process by using the npx command. Previously, I had success with a similar package created with node.js and inquirer. When running the following comma ...

When the keyboard appears, the Ionic 2 form smoothly slides upwards

I am currently working with the most recent version of Ionic 2. In my code, I have a <ion-content padding><form></form></ion-content> containing a text input. However, when attempting to enter text on an Android device, the keyboard ...

Angular 10 introduces a new feature where BehaviorSubject can now hold and emit two

Currently, I am attempting to log in using Firebase. The login system is functioning correctly; however, I am facing a challenge in retrieving the error name from the authentication service and displaying it in my login component. SignIn(email: string, pas ...

Effortlessly converting JSON data into TypeScript objects with the help of React-Query and Axios

My server is sending JSON data that looks like this: {"id" : 1, "text_data": "example data"} I am attempting to convert this JSON data into a TypeScript object as shown below: export interface IncomingData { id: number; t ...