Typescript feature allowing for the return of objects that adhere to a specified interface using wildcard return types

Within my coding framework, I have developed the following interface within an abstract base class:

interface MyInterface {
   field1: string
}

abstract class BaseClass {
   // some fields here...
   abstract serialize(): Array<MyInterface>
}

As I progress, I find myself creating multiple subclasses that inherit from BaseClass, each with unique additional fields. For example:

class Subclass1 extends BaseClass {
   // some additional fields that are not in BaseClass
   serialize(): Array<MyInterface> {
      return [{field1: "test", field2: "test2"}];
   }
}

At this point, I am seeking the appropriate return type for the serialize function, which should adhere to the idea of:

"return any object that implements MyInterface, including all fields required by MyInterface and possibly more"

While reviewing this setup, I encounter an error in Typescript due to the return type failing to comply with MyInterface. This is logical, as field2 is not part of the interface.

In essence, I believe I require something akin to a Java unbounded wildcard in Typescript, such as:

List<? extends MyInterface>

This signifies any object implementing MyInterface.

Answer №1

The main issue in your example is the absence of an array being returned.

On the other hand, if you attempt to explicitly return the array:

return [{field1: "test", field2: "test2"}];

You will encounter problems with excess property checks. (more information here).

Excess property checking occurs when assigning object literals to variables or passing them as arguments. If an object literal contains properties not found in the target type, errors will arise.

While it is acceptable to return an object with additional properties that match the return type, generating such objects ad hoc is discouraged by TypeScript because excess properties are deemed unnecessary and inaccessible.

Hence, instead of returning an directly instantiated object with unmatched properties, consider storing the object in an intermediate variable and returning that.

// Avoiding
// excess property checks by using an intermediate value.
const returnValue = [{field1: "test", field2: "test2"}];
return returnValue;

In practice, the data you wish to return likely already exists in a variable or field, making this problem more common in simple test scenarios.

Creating an object of mismatched shape solely for conforming to a narrower interface raises red flags, prompting TypeScript to intervene.

Access Playground Link

Answer №2

It appears that the solution lies in intersecting MyInterface with any additional fields you wish to include. By refining methods' return types in subclasses, it ensures that the return type still adheres to the base class.

class Subclass1 extends BaseClass {
   serialize(): Array<MyInterface & { field2: string }> {
      return [{field1: "test", field2: "test2"}]
   }
}

const foo = new Subclass1().serialize()
// (MyInterface & { field2: string })[]

View playground demo


If you prefer BaseClass to enforce this behavior, you can introduce generics and merge the extra props type within the class itself. This approach enhances clarity at the top level of the class regarding the additional data it may handle.

The return type annotation can now be eliminated as it is entirely enforced by BaseClass.

interface MyInterface { field1: string }

abstract class BaseClass<Extra = Record<string, never>> {
   abstract serialize(): Array<MyInterface & Extra>
}

class Subclass1 extends BaseClass<{ field2: string }> {
   serialize() {
      return [{field1: "test", field2: "test2"}]
   }
}

const foo = new Subclass1().serialize()
// { field1: string, field2: string }[]

NOTE: Record<string, never> may seem odd, but it essentially denotes an empty object which serves as a reasonable default for this generic type parameter.

Explore Playground

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

Issue with accessing undefined property in Angular 2+ using Typescript

In my Angular 7 project, I am retrieving data from a service which looks like this: {name: "peter", datetime: 1557996975991} I have a method that is supposed to retrieve this data: myMethod() { this.myService.getdata().subscribe((res) = ...

What are the steps to implement the `serialport` library in `deno`?

After tinkering with Deno to extract readings from an Arduino, I encountered a roadblock when it came to using the serialport library correctly. Here is what I attempted: According to a post, packages from pika.dev should work. However, when trying to use ...

Merging Promises in Typescript

In summary, my question is whether using a union type inside and outside of generics creates a different type. As I develop an API server with Express and TypeScript, I have created a wrapper function to handle the return type formation. This wrapper fun ...

Improved ergonomics for enhancing TypeScript union-narrowing typeguard function

Within our codebase, we have a utility that generates a typeguard to narrow down a discriminated union: export type ExtractBranchFromUnion< UNION, DISCRIMINANT extends keyof UNION, BRANCH extends UNION[DISCRIMINANT], > = UNION extends Record< ...

The function is not defined for this.X in TypeScript

I am currently developing an application using Angular 6. Within my app, I have the following code snippet: import { Component, OnInit } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Component({ selector: ...

How to turn off automatic password suggestions in Chrome and Firefox

Currently, I have integrated a 'change password' feature which includes fields for 'old password', 'new password', and 'retype password'. However, the autocomplete feature is suggesting passwords from other user acco ...

Run a function from an alternate element

I have successfully created a grid with a button that enables me to control a timer. When I click on the start button in the grid on the home page, the timer begins counting the time. By using a service, I am able to determine whether the timer is active ...

Beautiful parentheses for Typescript constructors

I'm working on a project where I've installed prettier. However, I've noticed that it always reformats the code snippet below: constructor(public url: string) { } It changes it to: constructor(public url: string) {} Is there any way to sto ...

Matching packages with mismatched @types in Webpack 2: A comprehensive guide

Having trouble implementing SoundJS (from the createJS framework) in my TypeScript project using webpack 2. In my vendors.ts file, I have the following import: import "soundjs"; Among other successful imports. The @types definitions installed via npm a ...

The type 'xxxx' is not compatible with the parameter type 'JSXElementConstructor<never>'

I am currently enrolled in a TypeScript course on Udemy. If you're interested, you can check it out here. import { connect } from 'react-redux'; import { Todo, fetchTodos } from '../actions'; import { StoreState } from '../red ...

Sharing parameters between pages in Angular IonicPassing parameters between pages within an Angular Ionic application

Is there a way to pass parameters from the signup page to the signupotp page successfully? I am facing an issue where the OTP on the signupotp page is not being recognized because the parameters (email and mobile) are not getting passed properly. In my bac ...

TypeScript compiler encountering issue with locating immutable.js Map iterator within for of loop

I am currently facing a challenge with using immutable.js alongside TypeScript. The issue lies in convincing the TypeScript compiler that a Map has an iterator, even though the code runs smoothly in ES6. I am perplexed as to why it does not function correc ...

Tips for sending a parameter to an onClick handler function in a component generated using array.map()

I've been developing a web application that allows users to store collections. There is a dashboard page where all the user's collections are displayed in a table format, with each row representing a collection and columns showing the collection ...

How can we implement type guarding for a generic class in TypeScript?

Implementing a generic class in TypeScript that can return different types based on its constructor parameter. type Type = 'foo' | 'bar'; interface Res { 'foo': {foo: number}; 'bar': {bar: string}; } class ...

Can the tooltip placement be adjusted in ng-bootstrap when it reaches a specific y-axis point?

Currently, I am facing an issue with my tooltip appearing under the header nav-bar instead of flipping to another placement like 'left-bottom' when it reaches the header. Is there a way to manually set boundaries for tooltips in ng-bootstrap? Unl ...

Utilizing vue-property-decorator: Customizing the attributes of @Emit

After seeing the @Emit feature, I checked out the example on GitHub. import { Vue, Component, Emit } from 'vue-property-decorator' @Component export default class YourComponent extends Vue { count = 0 @Emit() addToCount(n ...

ReactJS Error: The property 'hubConnection' is not defined on type 'JQueryStatic'

I am currently working with the Signalr library in React, but I keep encountering the following error: Property 'hubConnection' does not exist on type 'JQueryStatic'. Is there a solution to this issue? declare var window : any; import ...

Converting nested arrays to objects via JSON transformation

I am faced with a nested JSON array structure like this: Parent: { Child1: [ {name:'grandchild1', value:'abc', checked:true}, {name:'grandchild2', value:'pqr', checked:false} ], Ch ...

Waiting for asynchronous subscriptions with RxJS Subjects in TypeScript is essential for handling data streams efficiently

Imagine having two completely separate sections of code in two unrelated classes that are both listening to the same Observable from a service class. class MyService { private readonly subject = new Subject<any>(); public observe(): Observable&l ...

Angular 5 experiencing issues with external navigation functionality

Currently, I am attempting to navigate outside of my application. I have experimented with using window.location.href, window.location.replace, among others. However, when I do so, it only appends the href to my domain "localhost:4200/". Is it possible th ...