Destructuring objects with default values from two related interfaces

In my project, I have defined two interfaces called User and BankUser. The structure of the interface for BankUser looks like this:

interface BankUser extends User {
  banks: { [bank_id: string]: string};
  isSuper: boolean;
}

I am working on a function where I need to pass either a User or a BankUser, and I want the output to always be a BankUser. If the input is a User, it should automatically include the default properties for a BankUser.

const cleanedUser = (user: User | BankUser): BankUser => {
  const {uid, displayName, email, phoneNumber, photoURL, banks = {}, isSuper = false} = user;
  return {uid, displayName, email, phoneNumber, photoURL, banks, isSuper} as BankUser;
}

When trying to implement this function, I encountered two TS2339 errors stating that 'banks' and 'isSuper' do not exist on type User | BankUser. An example of the error message is:

TS2339: Property 'banks' does not exist on type User | BankUser

While adding // @ts-ignore above the function resolves the issue temporarily, I prefer to find a more elegant solution. Can someone suggest a better way to handle this situation?

Answer №1

When dealing with TypeScript, it is important to note that the compiler does not like looking up a property of an object if it's not explicitly defined in the object's type. In this situation, the User type does not explicitly mention the properties banks or isSuper, resulting in a compiler error. Although a BankUser may have these properties, when working with a variable that could be either a User or a BankUser, TypeScript considers it an error.

To resolve this issue and inform the compiler about the expected properties, you can treat the variable as something more specific than just User | BankUser. One approach is to use

User & Partial<BankUser>
, which ensures all properties of User are present along with optional properties from BankUser.

const cleanedUser = (user: User | BankUser): BankUser => {
    const {
        uid, displayName, email, phoneNumber, photoURL, banks = {}, isSuper = false
    } = user as User & Partial<BankUser>; // no error now
    return { uid, displayName, email, phoneNumber, photoURL, banks, isSuper };
}

Another way to implement cleanedUser is by using spread syntax:

const cleanedUser2 = (user: User | BankUser): BankUser => {
    return { banks: {}, isSuper: false, ...user };
}

This method requires less type manipulation and includes less typing. However, caution is advised when dealing with variables that may have additional properties beyond what is specified in their types, as unexpected behavior can occur.


It's crucial to handle cases where a value might possess extra properties that aren't explicitly defined in its type. Failure to do so can lead to unpredictable results during operations:

interface Hmm extends User {
    isSuper: number;
}
const hmm: Hmm = {
    uid: "", email: "", displayName: "", phoneNumber: "", photoURL: "", isSuper: 100
};
const beCareful = cleanedUser(hmm);
console.log(beCareful.isSuper) // Boolean at compile time, number at runtime !!!

Always exercise caution to avoid unexpected outcomes.


I hope this explanation helps you navigate through TypeScript more effectively. Good luck!

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

The functionality of Angular 6 Material Nested Tree is disrupted when attempting to use dynamic data

In Angular 6, I am utilizing mat-tree along with mat-nested-tree-node. My objective is to dynamically load the data when the user toggles the expand icon. Attempting to apply the dynamic data concept from the Flat Tree example provided in Material Example ...

What is the functionality of input onChange in React when using state management?

Whenever I try to log the value of the input after each onChange event, there seems to be an odd one event delay. For example, if 'some text' is the default input value and I remove the letter 't' by pressing backspace/delete, it first ...

Issues with the functionality of Angular 5 EventEmitter

I have been trying to call a function from the Parent Component in the Child Component, and here is how I implemented it: project-form.component.ts @Component({ selector: 'app-project-form', templateUrl: './project-form.component.html& ...

Having difficulty initializing a new BehaviourSubject

I'm struggling to instantiate a BehaviourSubject I have a json that needs to be mapped to this Typescript class: export class GetDataAPI { 'some-data':string; constructor (public title:string, public description:string, ...

Encountering CORS error when making a call to AWS API from Angular 9

Currently, I am working on implementing a post method in Angular 9 that interacts with an AWS API. However, upon calling the post function in Angular 9: this.http.post(url, body, requestOptions), I encountered an error in my browser stating: Access to XMLH ...

What is the method for creating pipes that filter multiple columns?

My pipe is designed to work exclusively for the "name" column and not for the author anymore. transform(items: Book[], filter: Book): any { if (!items || !filter) { return items; } // Filter items array; keep items that match and retu ...

What is the best way to access all the attributes (excluding methods) of an object in a class instance?

My goal is to generate a new object type that inherits all the properties from an existing class instance. In other words, I am looking for a way to transform a class instance into a plain object. For example, consider the following scenario: ...

The expected React component's generic type was 0 arguments, however, it received 1 argument

type TCommonField = { label?: string, dataKey?: string, required?: boolean, loading?: boolean, placeholder?: string, getListOptionsPromissoryCallback?: unknown, listingPromissoryOptions?: unknown, renderOption?: unknown, getOptionLabelFor ...

Discover the data type of the subfield within an interface or type in Typescript

Check out the interface and type declarations provided below: interface Foo { bar: { a: number b: string } } type Foo = { bar: { a: number b: string } } Is it possible to obtain the type definitions for "baz"? This will allow us ...

What is the method for defining the type of a variable without assigning a value to it?

Working on an Angular 11 project using Typescript with Strict Mode, I encountered the following issue: export class AvatarComponent { @Input() user: UserModel = null; } This resulted in a compilation error: Type 'null' is not assignable to ty ...

The interpolated string type is not allowed to be utilized for indexing a record that has the identical type as the key

I'm attempting to utilize an interpolated string form to access a Record type using a key that should match the record's key type. Unfortunately, it doesn't appear to be functioning as expected. Here is a simple example: type TypeOfKey = `c ...

What role does typescript play in this approach?

test.js const testList = [1, 2, 2, 4, 5, 2, 4, 2, 4, 5, 5, 6, 7, 7, 8, 8, 8, 1, 4, 1, 1]; const lastIndex = testList.findLastIndex((e:number) => e === 100); // Property 'findLastIndex' does not exist on type 'number[]'. Did you mean ...

What is the best way to define multiple variables in ionic 2 using Angular2 and TypeScript?

I'm brand new to working with ionic2/Angular2/Typescript. My project involves creating a wheel with eight slices, but I'm struggling with how to declare multiple variables. In JavaScript, I've declared them like this: function rand(min, max ...

Issues with the typings for the toPromise function in WebStorm have been identified

I'm encountering an issue with WebStorm not recognizing the typings for the toPromise function on 'rxjs', despite having updated it. Is there a way I can troubleshoot this and fix it? Strangely, the code still runs successfully despite the ...

Comparing values between two JSON objects in Angular 8: A guide

I need to compare the values of two objects, obj1 and obj2, by ignoring keys that are missing in either object. If all key-value pairs are equal, return false; otherwise, return true. For example: If 'id' is present in obj1 but not in obj2, it s ...

When using React and Material UI, there seems to be an issue with the Popover component where calling `setAnchorEl(null)` on the onClose event does not properly

I am encountering an issue with a Popover (imported from MaterialUI) nested inside a MenuItem (also imported from MaterialUI). The open prop for the popover is set to the boolean value of anchorEl. The onClose function is supposed to handle setting anchorE ...

What is causing the Typescript compiler to interpret an element in a string array as the type 'never'?

My Typescript function compiled without issue in version 3.5.3, but after updating to 3.8.3, it now throws a confusing error during compilation. import { isNumber, toInteger, padNumber } from './math'; parse(value: string): NgbDateStruct { if ...

Do we need to import Vue in every component when using Nuxt with TypeScript?

I recently integrated TypeScript into Nuxt using the guidelines provided in the documentation: However, I have a specific question regarding component setup. Should I always include import vue from "vue" and export default Vue.extend ({}); in al ...

Issue with populating virtual IDs in NestJS mongoose schema containing an array of schemas

In the schema provided below, I have defined the structure for Map, Marker, and Desk: export type MapDocument = Map & Document @Schema({ timestamps: true, versionKey: false, id: true }) export class Map { constructor(partial?: Partial< ...

I am looking for a guideline that permits me to restrict the use of a form validation tool

We have developed our own version of the Validators.required form-validator that comes with Angular 7, but now we need to switch to using CustomValidators.required. To enforce this change, we are considering banning the use of the old Validators.required b ...