Specifications for TypeScript Columns

Is there a way to create a comprehensive column definition map for various model types with proper typings in JavaScript? Let's explore how to achieve this:

export type User = {
  id: number;
  admin: boolean;
  email: string;
};

export type Book = {
  id: number;
  title: string;
  author: string;
  userId: number;
};

export type UserUID = {
  id: string;
  admin: boolean;
  email: string;
};

type DataRow = User & Book & UserUID;

export interface ColumnDefinition<F extends keyof DataRow = keyof DataRow> {
  valueFormatter?: (value: DataRow[F]) => string;
}

type ColumnDefinitionMap = {
  [key in keyof DataRow]: ColumnDefinition<key>;
};

const columnDefinitions: ColumnDefinitionMap = {
  admin: {
    // typed correctly
    valueFormatter: (value) => (value === true ? 'Admin' : 'User'),
  },
  id: {
    // should be number | string via models from above
    // currently is "never"
    valueFormatter: (value) => typeof value === 'string' ? value : value.toString(),
  },
};

Switching the DataRow to a Union causes issues as well since it only gives me the id column as that is the only common one between the three models.

Exploring potential solutions and strategies to address this challenge. Any insights or alternative approaches are welcome.

Answer №1

In case you come across an object that embodies both a User and a Book while also serving as a UserId, using an intersection is not advisable. Instead, the appropriate data type to use would be the union of these types:

type DataRow = User | Book | UserUID;

It's worth noting that attempting to access a key in a union-typed object that only exists in certain members of the union will lead to complications. Object types such as interfaces are open and can include properties unknown to the compiler; for instance, the author property of a User may not necessarily be missing but could hold any value.


There are two potential ways to handle this type of situation.

One workaround to avoid unexpected any properties entirely involves using a type like ExclusifyUnion, which is elaborated in the answer provided here. This method entails explicitly indicating any unanticipated properties from other union members as missing in each union member, translating to them being undefined if accessed:

type DataRow = ExclusifyUnion<User | Book | UserUID>;

The revised structure ensures that every union member clearly defines each property key, with many marked as optional-and-undefined.

With this type in place, most of your code should function smoothly, though you may encounter undefined types for columns not present in all models.

const columnDefinitions: ColumnDefinitionMap = {
  // Add mappings here
};

While the value parameter within id's valueFormatter maintains the type string | number, the equivalent for admin reflects boolean | undefined, anticipating scenarios where you process the admin field of a Book. To rectify this, consider altering the type of value from DataRow[K] to

Exclude<DataRow[K], undefined>
utilizing the Exclude utility type.


The alternative approach involves retaining the original union but leveraging type functions to represent 'a key from any member of the union' and 'the resultant type when indexing into an object of a union type with a key, disregarding unidentified union members':

type AllKeys<T> = T extends any ? keyof T : never;
type Idx<T, K> = T extends any ? K extends keyof T ? T[K] : never : never;

These functions act on unions by processing individual members following distributional conditional logic.

The resultant types look like this:

export interface ColumnDefinition<K extends AllKeys<DataRow>> {
  // Define properties here
}

type ColumnDefinitionMap = {
  [K in AllKeys<DataRow>]?: ColumnDefinition<K>;
};

Subsequently, your code should operate as anticipated:

const columnDefinitions: ColumnDefinitionMap = {
  // Include definitions
};

These strategies cater to different requirements, yet regardless of the chosen method, it is essential to work with a union rather than an intersection.

Link to Playground containing 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

I am searching for the vector geometric shapes svg elements that are commonly utilized in design editors. Can you point

When it comes to design editors, there are plenty of options such as Canva, Vistacreate, and Adobe Express. Each one uses a variety of styles for elements/objects/shapes. Are there any databases where I can find these resources? Some popular places include ...

Can you explain the distinction between inheritance and union types in regards to portraying polymorphism?

Exploring union types in Typescript, I have discovered a new way to showcase polymorphism without relying on inheritance. In my previous Java background, I would typically approach it like this: interface Emotion { display(): string; } class Joy impleme ...

The TypeScriptLab.ts file is generating an error message at line 23, character 28, where it is expecting a comma

I am attempting to convert a ts file to a js file. My goal is to enter some numbers into a textarea, and then calculate the average of those numbers. However, I encountered an error: TypeScriptLab.ts(23,28): error TS1005: ',' expected. I have in ...

Creating a universal wrapper function to serve as a logging tool?

Currently, I am working on a generic JS function that can wrap any other function. The purpose of this wrapper is to execute the wrapped function, log the input and output events, and then return the output for "transparent" logging. However, as I attempt ...

Creating a nested type using template literal syntax

Given a two-level nested type with specific properties: export type SomeNested = { someProp: { someChild: string someOtherChild: string } someOtherProp: { someMoreChildren: string whatever: string else: string } } I am looking ...

Generate a unique Object URL for the video source by utilizing the binary string obtained from the backend

I've been facing an issue with loading binary video data from my backend using fastAPI. When I curl the endpoint and save the file, it plays perfectly fine on my laptop. For the frontend, I'm using React+Typescript. I fetch the binary video data ...

Click function for mat-step event handler

Is it feasible to create a click event for the mat-step button? I want to be able to add a (click) event for each mat-step button that triggers a method. Essentially, I am looking to make the mat-step button function like a regular button. You can find mo ...

Beginner's Guide: Building your debut JavaScript/TypeScript library on GitHub and npm

I am looking to develop a simple JavaScript/TypeScript library focused on color conversion. Some of the functions and types I aim to export include: export type HEX = string; export type RGB = { r: number; g: number; b: number }; export type RGBA = { r: n ...

Angular 5.2: Component type does not contain the specified property

In my Formbuilder.Group method, I have included the properties as shown in the following TypeScript code: this.form = this.fb.group({ caseNumber: ['', [Validators.required, Validators.minLength(3), Validators.maxLength(50), Val ...

Having trouble viewing the initial value in an AngularJS2 inputText field

I'm having trouble displaying the initial value in inputText. I'm unsure of what mistake I'm making, but the value should be showing up as expected. Kind regards, Alper <input type="text" value="1" [(ngModel)]="Input.VSAT_input1" name= ...

Header Express does not contain any cookies, which may vary based on the specific path

In my express.js app, I have two controllers set up for handling requests: /auth and /posts. I've implemented token authorization to set the Authorization cookie, but I'm encountering an issue when making a request to /posts. The request goes th ...

Guide to importing items from a personalized library with extensive exporting

After updating my library structure by adding some directories, I am facing an issue with importing types from it. Below is the updated structure of the library : |-dist (built typescript) |-src |--business |---entities |----user.ts |----site.ts |---useca ...

Using TypeScript path aliases to resolve import errors

After creating a Vue.js project using Vue CLI 3 and setting up the import statement with the @ alias, I encountered an error when running npm run build. Can you explain why this happened? Error Message $ npm run build > [email protected] build / ...

Introducing NextAuth: Empowering multiple users to access a single account

I've been pondering if it's possible to have multiple users for a single account provider. One idea is to create a session with a specific field indicating the active user (the one currently logged in) and then allow for easy switching between us ...

Typescript's Integrated Compatibility of Types

One important concept I need to convey is that if one of these fields exists, then the other must also exist. When these two fields are peers within the same scope, it can be challenging to clearly communicate this connection. Consider the example of defi ...

Retrieve the 90 days leading up to the current date using JavaScript

I've been searching for a way to create an array of the 90 days before today, but I haven't found a solution on StackOverflow or Google. const now = new Date(); const daysBefore = now.setDate(priorDate.getDate() - 90); The result I'm looki ...

Angular 6 and D3 version 5.5 are causing an issue with the undefined `<variable>`

At the moment, I am attempting to create a Hierarchical Bar Chart in my Angular App using D3. When I click on a bar, I expect my function to recursively reshape the chart. The initial call works fine, but once I click on a bar, the variables become undefin ...

Issues with extensions during the Angular 9 Ivy compilation process

Good day! After upgrading a v8 project to v9, I encountered some errors related to extensions in the compiler. These errors are not present in another project that I also recently upgraded. The extensions, which are basic for constructors and prototypes, ...

Incorporate matTooltip dynamically into text for targeted keywords

I'm currently tackling a challenge in my personal Angular project that utilizes Angular Material. I'm struggling to find a solution for the following issue: For instance, I have a lengthy text passage like this: When you take the Dodge action, ...

Angular2 Service Failing to Return Expected Value

It's frustrating that my services are not functioning properly. Despite spending the last two days scouring Stack Overflow for solutions, I haven't been able to find a solution that matches my specific issue. Here is a snippet of my Service.ts c ...