I want to create an interface whereby I can extend any typescript type to add a property to extend a generic type. For example, create a Confidence<any>
type that looks like this:
export interface Confidence<T> extends T{
confidenceLevel:number
}
It can then be accessed by going:
const date:Confidence<Date> = new Date();
date.confidenceLevel = .9;
This does not seem to be possible (and I get the feeling it is anti-pattern), however I can do
export type Confidence<T> = T & {
confidenceLevel:number
}
Which appears to accomplish what I want to do though I feel like I'm cheating doing this.
I recognize this "reverse extension" could be problematic if I overwrite a property of the generic type, but is that the only concern? I'm having trouble wrapping my head around this, what is the best way to create a type that merely adds a property?
There is a longstanding feature request, microsoft/TypeScript#2225, which asks for the ability to do interface Foo<T> extends T {...}
the way you are asking for it. It's not implemented yet (and might never be). Right now you can only make an interface that extends
an object type with statically known keys. The unspecified generic type parameter T
does not work because the compiler cannot predict what keys it will have.
Note that you cannot fix this by trying to constrain the type parameter like interface Foo<T extends {}> extends T {....}
. That will just give you a misleading error message about constraints, as reported in microsoft/TypeScript#57037, and the same problem where T
doesn't have statically known keys persists (because T extends U
doesn't restrict the keys of T
to the keys of U
; it might well have more properties).
You should rest assured that an intersection is a reasonable way of achieving this (with the caveat that you will not be warned if your added property conflicts with a property of T
, as you noticed). It is the suggested solution in the aforementioned feature request. Additionally, the TypeScript library typings for Object.assign()
use an intersection to represent the type of the result of adding properties to an existing object. Therefore, with Confidence<T>
defined like
type Confidence<T> = T & {
confidenceLevel: number
}
you can easily write a function that produces one of these like this:
function makeConfidence<T>(x: T, confidenceLevel: number): Confidence<T> {
return Object.assign(x, { confidenceLevel });
}
and use it:
const x = makeConfidence(new Date(), 0.9); // x is a Confidence<Date>;
console.log(x.getFullYear()); // 2020
console.log(x.confidenceLevel); 0.9
Looks good.