The SortKey<> function is ineffective, even though the individual types it uses work perfectly fine

After my initial inquiry about TypeScript, the focus shifted to this current topic. In my previous question, I was attempting to develop a generic sort() method that could function with an array of sort keys defined by the SortKey type featured there (and here, it seems). The solution achieved in the previous query only caters to direct properties of the type.

Now, I sought to take this further and explore the possibility of making it recursive so that I could specify keys on nested objects. Here is the progress I have made:

type Comparer<T> = (a: T, b: T) => number;

// Code continues...
    

Now, one might wonder how I can ascertain that the "individual types" utilized within SortKey are functioning correctly. Let me illustrate:

// Various data models are defined here.

// Testing proceeds as follows...

https://i.sstatic.net/S7mTh.png

So, logically speaking, one would assume that

type DomainSortKey = SortKey<Domain>
should operate flawlessly. However, surprisingly enough, TypeScript declares that DomainSortKey === unknown.

The burning question remains: What exactly went wrong?

Answer №1

If you have a mapped type like this where keys in the form of {[K in keyof T]: ⋯} are used, it signifies a homomorphic mapped type as discussed in this article on "homomorphic mapped types". Essentially, this means that optional and readonly properties from the input will carry over to the output type with optional properties always including undefined.

In your case, the KeyPath<T> type will include undefined when any keys or subkeys (due to recursion) of T are optional. You can verify this by testing the type against {} | null | undefined, which is similar to unknown:

type TestKey = KeyPath<Domain> & ({} | null | undefined);
//   ^? type TestKey = "id" | "value" | "user" | "user.id" | "user.name" |
//      "user.emails" | "user.isActive" | "user.type" | "user.name.first" | 
//      "user.name.last" | undefined

While TypeScript should ideally prevent using undefined as a key in an indexed access type, there are cases where it doesn't catch such issues due to complex generics:

type Hmm<T> = { [K in keyof T]: K }[keyof T]
type Grr<T> = T[Hmm<T>]; // <-- I should be an error but it's not
type Okay = Grr<{a: string}> // string
type Bad = Grr<{ a?: string }> // unknown

The compiler ends up with unknown when trying to resolve {a?: string}[undefined], despite the known limitation explained in this comment on microsoft/TypeScript#56515. To address this issue, a recommended fix is provided involving the use of the mapping modifier -?:


To eliminate undefined from the list of keys, the -? modifier from the Required<T> utility type is utilized as follows:

type KeyPath<T, TParentKey = undefined> = {
  [K in keyof T]-?: K extends string ? (
    //          ^^
    TParentKey extends undefined ? `${K}` : `${TParentKey & string}.${K}`
  ) : never; }[keyof T]
  | {
    [K in keyof T]-?: T[K] extends object ? (
      //          ^^
      K extends string ? KeyPath<
        T[K], TParentKey extends undefined ? `${K}` : `${TParentKey & string}.${K}`
      > : never
    ) : never;
  }[keyof T];

This modification ensures that undefined is removed from keys, consequently fixing the issue with SortKey:

type DomainSortKey = SortKey<Domain>;
/* type DomainSortKey = {
    key: "id";
    order: 'asc' | 'desc';
    getter: Getter<Domain, "id">;
    comparer: Comparer<number>;
} | {
    key: "value";
    order: 'asc' | 'desc';
    getter: Getter<Domain, "value">;
    comparer: Comparer<...>;
} | ... 7 more ... | {
    ...;
} */

Feel free to test out the code on the Playground link here

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

Modify the [src] attribute of an image dynamically

I have a component that contains a list of records. export class HomeComponent implements OnInit { public wonders: WonderModel[] = []; constructor(private ms: ModelService){ ms.wonderService.getWonders(); this.wonders = ms.wonder ...

Comparing JSON objects with JavaScript models: A guide

Currently I'm working with angular 2 and I have an array of data. data: MyModel[] = [ { id: 1, name: 'Name', secondName: 'SecondName' } In addition, I have created the interface MyModel: interface MyModel { id: number, nam ...

What is the equivalent of defining conditional string types in Typescript similar to flow?

type UpsertMode = | 'add' | 'update' | 'delete'; interface IUpsertMembers { mode: UpsertMode; } const MagicButton = ({ mode: UpsertMode }) => { return ( <button>{UpsertMode}</button> ); } const Upse ...

ng2-toastr in conjunction with akveo/ng2-admin - Styles not being applied

I recently integrated ng2-toastr into my akveo/ng2-admin dashboard, utilizing the latest version with Angular 4. Following the provided installation documentation, I imported the necessary CSS in the index.html file and declared ToastModule in the app.modu ...

What is the best way to modify the underline style of a tab in Material UI?

I'm trying to customize the underline of: https://i.stack.imgur.com/J2R1z.png Currently, I am using material ui version 4.12.3 The code snippet for generating my tabs is below: function renderTabs(): JSX.Element { return ( <Tabs className={cla ...

Checking for unnecessary properties in Typescript with vue-i18n

Let's consider two different languages represented in JSON format: jp.json { "hello": "こんにちは" } ge.json { "hello": "hallo", "world": "welt" } Now, we are going to com ...

Creating a nested observable in Angular2: A step-by-step guide

Currently, I am exploring a new approach in my Angular2 service that involves using observables. The data source for this service will initially be local storage and later updated when an HTTP call to a database returns. To achieve this dynamic updating of ...

What is the best way to pass a value to a modal and access it within the modal's component in Angular 8?

How can I trigger the quickViewModal to open and send an ID to be read in the modal component? Seeking assistance from anyone who can help. Below is the HTML code where the modal is being called: <div class="icon swipe-to-top" data-toggle="modal" da ...

Using Angular 2/4/5 to Bind UTC-formatted Date to Datepicker

As someone who is just starting out with Angular and Angular Material, I have encountered an issue regarding zonedDate format for dates in my backend. The backend requires dates to be in zonedDate Format like this: 2018-04-11T02:12:04.455Z[UTC]. However, ...

Having trouble with the react event handler for the renderedValue component in Material UI?

I am facing an issue while trying to utilize the onDelete event handler within the chip component using Material UI in the code snippet below. Upon clicking on the chip, it triggers the Select behavior which opens a dropdown menu. Is there a way to modif ...

What is the best way to transition a connected component from a class-based to a functional component in TypeScript?

I'm in the process of switching from a class-based component to a functional component. This is a connected component that uses mapState. Here is my initial setup: import { connect } from 'react-redux' import { fetchArticles } from '. ...

Guidelines for implementing more rigorous type checks in TypeScript:

I am looking to enhance the code validation process and eliminate any implicit 'any' types. To achieve this, I have set "strict": true in my tsconfig.json. { "compilerOptions": { "target": "ES5", ...

Is it possible for a class that implements an interface to have additional fields not defined in the parent interface?

Looking at the code snippet below, my aim is to ensure that all classes which implement InterfaceParent must have a method called add that takes an instance of either InterfaceParent or its implementing class as input and returns an instance of InterfacePa ...

Tips for integrating and utilizing the MSAL (Microsoft Authentication Library for JavaScript) effectively in a TypeScript-based React single page application

Issue I'm encountering difficulties importing the MSAL library into my TypeScript code. I am using the MSAL for JS library with typings in a basic TypeScript/React project created using create-react-app with react-typescript scripts. As someone who i ...

Angular - Executing a function in one component from another

Within my Angular-12 application, I have implemented two components: employee-detail and employee-edit. In the employee-detail.component.ts file: profileTemplate: boolean = false; contactTemplate: boolean = false; profileFunction() { this.profileTempla ...

The Battle of Extends and Intersection in Typescript

Typescript's concept of extension is akin to C++'s inheritance. Intersection in Typescript involves creating a new object with all the properties from the intersected classes. Why utilize intersection when extends keyword can already merge ...

I am currently facing an issue related to the length property. It is showing an ERROR TypeError: Cannot read property 'length' of undefined

Is it recommended to set the length to be inherited from Angular right? If so, why am I getting this error: "MyPostsComponent.html: 7 ERROR TypeError: Cannot read the 'length' of undefined property" when fileList.length is greater than 0? onFile ...

Steps for aligning the upper rectangular text in the center of the larger rectangular border

https://i.stack.imgur.com/7yr5V.png I was aware of a particular element in html that had text positioned in the upper left corner, but my knowledge didn't go beyond that. Should I be adjusting the translation on both the X and Y axes based on the par ...

Is there a way to modify a suffix snippet and substitute the variable in VS Code?

While working on my Java project in VS Code, I stumbled upon some really helpful code snippets: suffix code snippets Once I type in a variable name and add .sysout, .cast, or similar, the snippet suggestion appears. Upon insertion, it translates to: res ...

Establishing specific categories for a universal element

I have been working on creating an input component that functions as a custom select for enums in my application. I have tried defining them for different types using concise one-liners but have run into various typing issues. Here is what I have so far: ...