Combining Interfaces in Typescript: Utilizing Union Types with a Base and Extended Interface

I'm facing an issue with the following code snippet

interface BaseA {
  a: number;
}

interface SpecialA extends BaseA {
  b: number;
}

type A = BaseA | SpecialA

const a = {
    a: 5, b: 5
} as A

console.log(a.b)

Even though I thought the code was correct, I encountered the error message

Property 'b' does not exist on type 'A'.
  Property 'b' does not exist on type 'BaseA'

It appears that the actual definition of type A is not what I intended it to be. I was expecting it to match the definition below:

interface A {
  a: number;
  b?: number;
}

This leaves me with the following questions:

  1. What caused the discrepancy between the defined type A and the expected type A?
  2. Is there a way to define the expected type A without manually defining it?

Note: I am required to use the type SpecialA unchanged in certain parts of my code, so directly defining the expected A type is not feasible.

Answer №1

Given that A is a union, it is important to narrow down/discriminate your type in order to access members that are not shared.

interface BaseA {
  a: number;
}

interface SpecialA extends BaseA {
  b: number;
}

type A = BaseA | SpecialA

const a = {
  a: 5, b: 5
} as A

if ("b" in a) { // performing narrowing at this point
  console.log(a.b)
}

Playground

Answer №2

When you use as A, you are essentially stating that "The variable a could potentially hold a BaseA or a SpecialA." However, if you later attempt to access a.b without any type check, it becomes a type error because TypeScript cannot guarantee that a is specifically a SpecialA which contains the b property.

To address this issue, you should perform a check first:

if ("b" in a) {
    console.log(a.b);
}

Within the if block, TypeScript understands that a points to an instance of SpecialA.

It's worth noting that even though a is declared as a constant, the object it refers to can still be altered to remove the b property:

delete (a as any).b; // not recommended

If you do not intend to delete the b property, it's advisable to directly specify the type as SpecialA:

const a: SpecialA = {
    a: 5,
    b: 5,
};

Alternatively, you can use as const to indicate to TypeScript that the object will remain unaltered:

const a = {
    a: 5,
    b: 5,
} as const;

If you choose to go with as const, there is no need for a separate type annotation. TypeScript conducts structural type checking rather than nominal type checking, but adding a type like A can enhance clarity for other developers:

const a: A = {
//     ^^^
    a: 5,
    b: 5,
} as const;

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

Determine the accurate data type while iterating through a for loop

I am facing an issue where I have around 40 unique actions defined, all with the same parameters except for each being provided with a different schema which is causing the problem type ActionName = 'replaceText' | 'replaceImage'; type ...

Typescript not flagging an error for an object being declared without a type

I am encountering an issue with my tsconfig.json file: { "compilerOptions": { "allowJs": true, "allowSyntheticDefaultImports": true, "baseUrl": "src", "isolatedModules": true, "jsx": "preserve", "esModuleInterop": true, "forc ...

What is the official name of the key type for the Built-in Object?

There was a built-in type that I used in the past which represented the union of all possible object keys. It was named objectKey or something similar. Here is an example: type objectKey = string | number | symbol Unfortunately, I am drawing a blank on t ...

Does Typescript fail to recognize the "delete" operator?

Whenever I utilize the delete operator in Typescript, it appears that the system does not recognize that the property has been eliminated. For instance: interface HasName { name: string; } interface HasNoName { name: never; } function removeName( ...

What is an improved method for defining a TypeScript type to store API method invocations?

Currently, I am exploring ways to enhance either GenericActionables or Items in a way that eliminates the need to manually add the API method name as a GenericActionables<"nameOfNewMethod"> to the Actionables type every time. Any suggesti ...

Ensuring type integrity for intersections containing varying numbers of elements

Currently, I am navigating a sophisticated custom typeguard library developed for a project I'm involved in. I am facing challenges in grasping the concept of function signatures used in typeguards. The library includes a generic Is function that has ...

Modifying the value of an object key with Javascript

The information I am working with is structured as follows: obj = { pref: { language: 'English', } }; My goal is to update the language field to 'Spanish'. ...

An error occurred while defining props due to a parsing issue with the prop type. The unexpected token was encountered. Perhaps you meant to use `{`}` or `}`?

const dataProps = defineProps({ selectedData: <Record<string, string>> }); Under the closing bracket, there is a red line indicating: Error: Unexpected token. Consider using {'}'} or &rbrace; instead?eslint Expression expecte ...

EventManager gathering of various subscriptions

Is it possible for JhiEventManager to handle multiple subscriptions, or do I need a separate subscription for each event? Will the destroy() method of JhiEventManager handle multiple subscriptions as well? export class SomeComponent implements OnInit, OnDe ...

Removing an attachment from the attachment object array nestled within a comment object array nested inside another object array

I am currently grappling with the challenge of removing an attachment from an array of attachments within a nested structure of comment objects. Despite several attempts, I have yet to find a solution that works effectively. export class CommentSection{ ...

A method for handling specific subsets of an enum in a secure and controlled manner

I have an enumerated type called Country and another type that represents a subset of European countries. I want to handle values within this subset differently from others. Currently, I am using an if statement with multiple conditions, but it could get u ...

What is the process for creating a new Object based on an interface in Typescript?

I am dealing with an interface that looks like this: interface Response { items: { productId: string; productName: string; price: number; }[] } interface APIResponse { items: { productId: string; produc ...

What steps can be taken to resolve the error "Incompatible types: TodoItem undefined cannot be assigned to type TodoItem"?

I am currently in the process of learning TypeScript. Here is what's inside todoItem.ts: export class TodoItem { constructor( public id: number, public task: string, public complete: boolean = false ) {} printDetails(): void { ...

What is the best way to replicate certain key-value pairs in an array of objects?

I am working with an array of objects. resources=[{name:'x', title:'xx',..},{name:'y',title:'yy',..}..] To populate my HTML tooltip, I am pushing all the titles from the resources array to a new array. dialogOkCli ...

Sequelize v5 & Typescript Model Loader

Having previous experience with Sequelize for projects (v4), I am now venturing into starting a new project using Sequelize v5 & Typescript. I have been following Sequelize's documentation on how to define Models at: https://sequelize.org/master/ ...

Is there a TypeScript alternative to triggering a click event on a specific class using $(".class").click()?

I am currently utilizing a date range picker within an Angular project. <button type="button" class="btn btn-danger daterange-ranges"> <i class="icon-calendar22 position-left"></i> <span></span> <b class="caret"></b ...

Error: Unable to retrieve the value of 'secret' as it is undefined when attempting to assign a response cookie in Express framework

Today's operation that I've carried out countless times seems to be going awry. For some reason, I am unable to set a refresh token cookie using Express. Here is the error message in full /home/me/Code/apGymBE/node_modules/express/lib/response.j ...

Defining the type of the createAction() function in TypeScript within the context of Redux Toolkit

Lately, I have been delving into the redux-toolkit library but I am struggling with understanding the type declaration of the createAction function as demonstrated below. The createAction function returns a PayloadActionCreator which includes a generic of ...

Customizing dropzone color while dragging an image in Angular

I have been using Angular and created an image input field. Within this input field, I implemented a dropzone that allows users to drag files into it. Is there a way for me to modify the background of the dropzone when a file is being dragged into it? Thi ...

What is the best way to retrieve an object within a class?

Exploring a Class Structure: export class LayerEditor { public layerManager: LayerManager; public commandManager: CommandManager; public tools: EditorTools; constructor() { this.commandManager = new CommandManager(); this.lay ...