Allowing the property of interface/type to not overlap

Is there a way to consolidate multiple interfaces/types in Typescript by merging them? The resulting interface should have all shared properties as mandatory and all unique properties as optional. Here is an example:

interface ShopUser {
   userId: string
   boughtItems: string[]
}

interface BlogUser {
   userId:string
   viewedBlogs: string[]
}

interface AppUser {
   userId: string
   appSessions: string[]
}

The desired outcome would resemble the following structure:

interface DBUser {
   userId: string          // mandatory, present in all child interfaces
   boughtItems?:string[]   // now optional
   viewedBlogs?:string[]   // now optional
   appSessions?:string[]   // now optional
}

I have attempted various methods such as:

type DBUser = ShopUser | BlogUser | AppUser // only userId is accessible. Other properties remain unknown

type DBUser = ShopUser & BlogUser & AppUser // results in all properties being mandatory...

Utilizing something like

Omit<ShopUser, "boughtItems">
followed by redefining does not seem very elegant, especially given the complexity of our interfaces. It ends up being messy and lacks reusability.

Answer №1

When combining two sets A | B, the keys that are accessible are those shared by both sets A and B. The expression keyof (A | B) provides us with these shared keys, resulting in the type A[K] | B[K] or (A | B)[K].

To obtain the non-shared keys, one can intersect the sets using A & B and exclude the common keys to derive the difference between the two types. Adding Partial makes all properties optional.

The generated type may become too intricate for TypeScript to portray accurately. To resolve this, a conditional type alongside a mapped type can compel TypeScript to "evaluate" the type.

type Merge<A, B> = {
    [K in keyof (A | B)]: A[K] | B[K]; // shared keys
} & (
    Partial<Omit<A & B, keyof (A | B)>> // the difference
        extends infer O ? { [K in keyof O]: O[K] } : never // improved display
);

Now, merge all three types together:

type DBUser = Merge<Merge<ShopUser, BlogUser>, AppUser>;

Check out the Playground link


An alternative approach is to create a type Merge that can handle a tuple of types and merge them similarly:

type DBUser = Merge<[ShopUser, BlogUser, AppUser]>;

However, it's up to the reader to explore and comprehend this method ;')

Answer №2

If this is the structure you need, ensure that userId is included while the rest of the properties are considered optional

type DatabaseUser = Partial<ShoppingUser | BloggingUser | ApplicationUser> & {
    [Key in keyof (ShoppingUser | BloggingUser | ApplicationUser)]: (ShoppingUser & BloggingUser & ApplicationUser)[Key]
};

Alternatively, you can define a merging type as shown below:

type Merge<X, Y> = Partial<X | Y> & {
    [Key in keyof (X | Y)]: (X & Y)[Key]
};

type DatabaseUser = Merge<Merge<ShoppingUser, BloggingUser>, ApplicationUser>;

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

experiencing an excessive amount of re-renders after transferring data to a distinct component

At the moment, I have implemented this logic to display data based on the results of a graphql query, and it is working well: const contacts = () => { const { loading, error, data } = useUsersQuery({ variables: { where: { id: 1 }, ...

Troubleshooting: Angular 6 Renderer2 Issue with Generating Dynamic DOM Elements for SELECT-Option

Currently, I am attempting to dynamically create a select option using Renderer2. Unfortunately, I am facing difficulties in creating the <Select></Select> element, but I can confirm that the <options> are being successfully created. Due ...

How can I upload multiple images in one request using Typescript?

HTML: <div> <input type ="file" (change)="selectFiles($event)" multiple="multiple" /> </div> Function to handle the change event selectFiles(event) { const reader = new FileReader(); if (event.target.files & ...

Issues arising post transitioning to 14.0.0 from 13.0.0 version of ngx-masonry library leading to failed tests

Following the update to the latest stable version of the library ngx-masonry 14.0.0, our tests started failing. The release was just yesterday (24.10.2022) and you can find the changelog here: https://github.com/wynfred/ngx-masonry/blob/master/CHANGELOG.md ...

Binding data to custom components in Angular allows for a more flexible

In my current scenario, I am looking to pass a portion of a complex object to an Angular component. <app-component [set]="data.set"></app-component> I want the 'data.set' object in the parent class to always mirror the 'set&apo ...

Specify the route in tsconfig.app.json for your Angular project

Using the angular CLI, the project has been created with the following folder structure: https://i.sstatic.net/lqGMo.png The aim is to establish a path to a bar folder in tsconfig.app.json and import Car to Garage. The tsconfig.app.json file: { "exte ...

How to set the type of an object property to a string based on a string from an array of strings in TypeScript

Great { choices: ['Bob', 'Chris', 'Alice'], selectedChoice: 'Alice', } Not So Good { choices: ['Bob', 'Chris', 'Alice'], selectedChoice: 'Sam', } I've been exp ...

How can I combine my two ngIf conditions into an ngIf else statement?

Having trouble incorporating an *ngIf else with two large <div> elements, as the content seems overwhelming to organize properly while following the documentation. Initially believed that using the same styling for two open text boxes, with one hidd ...

Utilize the key types of an object to validate the type of a specified value within the object

I am currently working with an object that contains keys as strings and values as strings. Here is an example of how it looks: const colors = { red: '#ff0000', green: '#00ff00', blue: '#0000ff', } Next, I define a type ...

Why is this statement useful? _ equals _;

While studying an Angular 7 class, I stumbled upon the following code that left me a bit confused. It's not exactly a search engine-friendly statement, so my apologies for that :) @Component({ selector: 'app-some', templateUrl: './ ...

What is the rationale behind permitting interface method implementations to have varying intersection type arguments?

The interface and implementation presented here are quite straightforward: class Transform { X: number = 0 Y: number = 0 } class RenderData { Model: object | null = null } interface System { Update(e: Transform & RenderData): void } class Ren ...

Having difficulty utilizing defineProps in TypeScript

For some time now, I've been utilizing withDefaults and defineProps. Unfortunately, this setup has recently started failing, leaving me puzzled as to why! Here's a simple SFC example: <script setup lang = "ts"> const props ...

What is the process for uploading an image with express-fileupload?

Looking to upload an image to Cloudinary via Postman using the express-fileupload library for handling multipart forms. Here is a snippet from my index.ts file: import fileUpload from "express-fileupload"; app.use(fileUpload()); In my controller ...

The promise object is displayed instead of the actual data retrieved from the API call

I am currently working on fetching data from an API and showcasing the name of the returned data on the front end. This function successfully retrieves the data through an API call: async function retrieveData(url){ var _data; let response = await fetch( ...

Elegantly intersect two types of functions in Typescript

Two function types are defined as follows: wrapPageElement?( args: WrapPageElementBrowserArgs<DataType, PageContext, LocationState>, options: PluginOptions ): React.ReactElement .. and .. wrapPageElement?( args: WrapPageElementNodeArgs<Data ...

Is there a way to detect changes in a Service variable within an Angular component?

One of my components contains a button that activates the showSummary() function when clicked, which then calls a service named Appraisal-summary.service.ts that includes a method called calc(). showSummary(appraisal) { this.summaryService.calc(appraisal ...

The saved editable input number is automatically pushed even without needing to click on save or cancel

I am working with a datatable, chart, and a label that shows the latest added value. The table and chart display time-series data for the last 30 minutes, including the timestamp and a random numerical value between 0 and 999. Every 10 seconds, a new data ...

Error: The AppModule encountered a NullInjectorError with resolve in a R3InjectorError

I encountered a strange error in my Angular project that seems to be related to the App Module. The error message does not provide a specific location in the code where it occurred. The exact error is as follows: ERROR Error: Uncaught (in promise): NullInj ...

Errors caused by Typescript transpilation only manifest on the production server

During the process of updating my node version and dependencies on both machines, I came across an issue where building my app in production on one machine resulted in an error, while building it on my main machine did not. I found that the errors disappe ...

Is it possible to verify type equality in Typescript?

If the types do not match, I want to receive an error. Here is an example of an object: const ACTIVITY_ATTRIBUTES = { onsite: { id: "applied", .... }, online: { id: "applied_online", .... }, ... } as co ...