Overriding interface property type defined in Typescript d.ts file
JavascriptTypescriptTypescript TypingsJavascript Problem Overview
Is there a way to change the type of interface property defined in a *.d.ts
in typescript?
for example:
An interface in x.d.ts
is defined as
interface A {
property: number;
}
I want to change it in the typescript files that I write to
interface A {
property: Object;
}
or even this would work
interface B extends A {
property: Object;
}
Will this approach work? It didn't work when I tried on my system. Just want to confirm if it's even possible?
Javascript Solutions
Solution 1 - Javascript
I use a method that first filters the fields and then combines them.
reference https://stackoverflow.com/questions/48215950/exclude-property-from-type
interface A {
x: string
}
export type B = Omit<A, 'x'> & { x: number };
for interface:
interface A {
x: string
}
interface B extends Omit<A, 'x'> {
x: number
}
Solution 2 - Javascript
type ModifiedType = Modify<OriginalType, {
a: number;
b: number;
}>
interface ModifiedInterface extends Modify<OriginalType, {
a: number;
b: number;
}> {}
Inspired by ZSkycat's extends Omit
solution, I came up with this:
> type Modify
Example:
interface OriginalInterface {
a: string;
b: boolean;
c: number;
}
type ModifiedType = Modify<OriginalInterface , {
a: number;
b: number;
}>
// ModifiedType = { a: number; b: number; c: number; }
Going step by step:
type R0 = Omit<OriginalType, 'a' | 'b'> // { c: number; }
type R1 = R0 & {a: number, b: number } // { a: number; b: number; c: number; }
type T0 = Exclude<'a' | 'b' | 'c' , 'a' | 'b'> // 'c'
type T1 = Pick<OriginalType, T0> // { c: number; }
type T2 = T1 & {a: number, b: number } // { a: number; b: number; c: number; }
v2.0 Deep Modification
interface Original {
a: {
b: string
d: {
e: string // <- will be changed
}
}
f: number
}
interface Overrides {
a: {
d: {
e: number
f: number // <- new key
}
}
b: { // <- new key
c: number
}
}
type ModifiedType = ModifyDeep<Original, Overrides>
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}
// ModifiedType =
{
a: {
b: string
d: {
e: number
f: number
}
}
b: {
c: number
}
f: number
}
Find ModifyDeep
below.
Solution 3 - Javascript
You can't change the type of an existing property.
You can add a property:
interface A {
newProperty: any;
}
But changing a type of existing one:
interface A {
property: any;
}
Results in an error:
> Subsequent variable declarations must have the same type. Variable > 'property' must be of type 'number', but here has type 'any'
You can of course have your own interface which extends an existing one. In that case, you can override a type only to a compatible type, for example:
interface A {
x: string | number;
}
interface B extends A {
x: number;
}
By the way, you probably should avoid using Object
as a type, instead use the type any
.
In the docs for the any
type it states:
> The any type is a powerful way to work with existing JavaScript, > allowing you to gradually opt-in and opt-out of type-checking during > compilation. You might expect Object to play a similar role, as it > does in other languages. But variables of type Object only allow you > to assign any value to them - you can’t call arbitrary methods on > them, even ones that actually exist:
let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)
let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
Solution 4 - Javascript
The short answer for lazy people like me:
type Overrided = Omit<YourInterface, 'overrideField'> & { overrideField: <type> };
interface Overrided extends Omit<YourInterface, 'overrideField'> {
overrideField: <type>
}
Solution 5 - Javascript
Extending @zSkycat's answer a little, you can create a generic that accepts two object types and returns a merged type with the members of the second overriding the members of the first.
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type Merge<M, N> = Omit<M, Extract<keyof M, keyof N>> & N;
interface A {
name: string;
color?: string;
}
// redefine name to be string | number
type B = Merge<A, {
name: string | number;
favorite?: boolean;
}>;
let one: A = {
name: 'asdf',
color: 'blue'
};
// A can become B because the types are all compatible
let two: B = one;
let three: B = {
name: 1
};
three.name = 'Bee';
three.favorite = true;
three.color = 'green';
// B cannot become A because the type of name (string | number) isn't compatible
// with A even though the value is a string
// Error: Type {...} is not assignable to type A
let four: A = three;
Solution 6 - Javascript
Omit
the property when extending the interface:
interface A {
a: number;
b: number;
}
interface B extends Omit<A, 'a'> {
a: boolean;
}
Solution 7 - Javascript
I have created this type that allows me to easily override nested interfaces:
type ModifyDeep<A extends AnyObject, B extends DeepPartialAny<A>> = {
[K in keyof A]: B[K] extends never
? A[K]
: B[K] extends AnyObject
? ModifyDeep<A[K], B[K]>
: B[K]
} & (A extends AnyObject ? Omit<B, keyof A> : A)
/** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
type DeepPartialAny<T> = {
[P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any
}
type AnyObject = Record<string, any>
And then you can use it like that:
interface Original {
a: {
b: string
d: {
e: string // <- will be changed
}
}
f: number
}
interface Overrides {
a: {
d: {
e: number
f: number // <- new key
}
}
b: { // <- new key
c: number
}
}
type ModifiedType = ModifyDeep<Original, Overrides>
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}
// ModifiedType =
{
a: {
b: string
d: {
e: number
f: number
}
}
b: {
c: number
}
f: number
}
Solution 8 - Javascript
For narrowing the type of the property, simple extend
works perfect, as in Nitzan's answer:
interface A {
x: string | number;
}
interface B extends A {
x: number;
}
For widening, or generally overriding the type, you can do Zskycat's solution:
interface A {
x: string
}
export type B = Omit<A, 'x'> & { x: number };
But, if your interface A
is extending a general interface, you will lose the custom types of A
's remaining properties when using Omit
.
e.g.
interface A extends Record<string | number, number | string | boolean> {
x: string;
y: boolean;
}
export type B = Omit<A, 'x'> & { x: number };
let b: B = { x: 2, y: "hi" }; // no error on b.y!
The reason is, Omit
internally only goes over Exclude<keyof A, 'x'>
keys which will be the general string | number
in our case. So, B
would become {x: number; }
and accepts any extra property with the type of number | string | boolean
.
To fix that, I came up with a different OverrideProps
utility type as following:
type OverrideProps<M, N> = { [P in keyof M]: P extends keyof N ? N[P] : M[P] };
Example:
type OverrideProps<M, N> = { [P in keyof M]: P extends keyof N ? N[P] : M[P] };
interface A extends Record<string | number, number | string | boolean> {
x: string;
y: boolean;
}
export type B = OverrideProps<A, { x: number }>;
let b: B = { x: 2, y: "hi" }; // error: b.y should be boolean!
Solution 9 - Javascript
It's funny I spend the day investigating possibility to solve the same case. I found that it not possible doing this way:
// a.ts - module
export interface A {
x: string | any;
}
// b.ts - module
import {A} from './a';
type SomeOtherType = {
coolStuff: number
}
interface B extends A {
x: SomeOtherType;
}
Cause A module may not know about all available types in your application. And it's quite boring port everything from everywhere and doing code like this.
export interface A {
x: A | B | C | D ... Million Types Later
}
You have to define type later to have autocomplete works well.
So you can cheat a bit:
// a.ts - module
export interface A {
x: string;
}
Left the some type by default, that allow autocomplete works, when overrides not required.
Then
// b.ts - module
import {A} from './a';
type SomeOtherType = {
coolStuff: number
}
// @ts-ignore
interface B extends A {
x: SomeOtherType;
}
Disable stupid exception here using @ts-ignore
flag, saying us the we doing something wrong. And funny thing everything works as expected.
In my case I'm reducing the scope vision of type x
, its allow me doing code more stricted. For example you have list of 100 properties, and you reduce it to 10, to avoid stupid situations
Solution 10 - Javascript
Date: 19/3/2021.
I think the latest typescript(4.1.2) version is supporting interface
override in d.ts
file.
// in test.d.ts
interface A {
a: string
}
export interface B extends A {
a: number
}
// in any ts file
import { B } from 'test.d.ts'
// this will work
const test: B = { a: 3 }
// this will not work
const test1: B = { a: "3" }
Solution 11 - Javascript
If someone else needs a generic utility type to do this, I came up with the following solution:
/**
* Returns object T, but with T[K] overridden to type U.
* @example
* type MyObject = { a: number, b: string }
* OverrideProperty<MyObject, "a", string> // returns { a: string, b: string }
*/
export type OverrideProperty<T, K extends keyof T, U> = Omit<T, K> & { [P in keyof Pick<T, K>]: U };
I needed this because in my case, the key to override was a generic itself.
If you don't have Omit
ready, see https://stackoverflow.com/questions/48215950/exclude-property-from-type.
Solution 12 - Javascript
If you only want to modify the type of an existing property and not remove it, then & is enough:
// Style that accepts both number and percent(string)
type BoxStyle = {
height?: string | number,
width?: string | number,
padding?: string | number,
borderRadius?: string | number,
}
// These are both valid
const box1: BoxStyle = {height: '20%', width: '20%', padding: 0, borderRadius: 5}
const box2: BoxStyle = {height: 85, width: 85, padding: 0, borderRadius: 5}
// Override height and width to be only numbers
type BoxStyleNumeric = BoxStyle & {
height?: number,
width?: number,
}
// This is still valid
const box3: BoxStyleNumeric = {height: 85, width: 85, padding: 0, borderRadius: 5}
// This is not valid anymore
const box4: BoxStyleNumeric = {height: '20%', width: '20%', padding: 0, borderRadius: 5}
Solution 13 - Javascript
NOTE: Not sure if the syntax I'm using in this answer was available when the older answers were written, but I think that this is a better approach on how to solve the example mentioned in this question.
I've had some issues related to this topic (overwriting interface properties), and this is how I'm handling it:
- First create a generic interface, with the possible types you'd like to use.
You can even use choose a default
value for the generic parameter as you can see in <T extends number | SOME_OBJECT = number>
type SOME_OBJECT = { foo: "bar" }
interface INTERFACE_A <T extends number | SOME_OBJECT = number> {
property: T;
}
- Then you can create new types based on that contract, by passing a value to the generic parameter (or omit it and use the default):
type A_NUMBER = INTERFACE_A; // USES THE default = number TYPE. SAME AS INTERFACE_A<number>
type A_SOME_OBJECT = INTERFACE_A<SOME_OBJECT> // MAKES { property: SOME_OBJECT }
And this is the result:
const aNumber: A_NUMBER = {
property: 111 // THIS EXPECTS A NUMBER
}
const anObject: A_SOME_OBJECT = {
property: { // THIS EXPECTS SOME_OBJECT
foo: "bar"
}
}