Overriding interface property type defined in Typescript d.ts file

JavascriptTypescriptTypescript Typings

Javascript 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 = Omit & R; > > // before [email protected] > type Modify = Pick> & R

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; }

TypeScript Utility Types


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:

  1. 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;
}
  1. 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"
    }
}

Typescript playground

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAbdul23View Question on Stackoverflow
Solution 1 - JavascriptZSkycatView Answer on Stackoverflow
Solution 2 - JavascriptQwertyView Answer on Stackoverflow
Solution 3 - JavascriptNitzan TomerView Answer on Stackoverflow
Solution 4 - JavascriptMasih JahangiriView Answer on Stackoverflow
Solution 5 - JavascriptryanjduffyView Answer on Stackoverflow
Solution 6 - JavascriptJmJView Answer on Stackoverflow
Solution 7 - Javascriptitay odedView Answer on Stackoverflow
Solution 8 - JavascriptAidinView Answer on Stackoverflow
Solution 9 - JavascriptEgor MalkevichView Answer on Stackoverflow
Solution 10 - Javascriptyongming zhuangView Answer on Stackoverflow
Solution 11 - JavascriptToniView Answer on Stackoverflow
Solution 12 - JavascriptPhierruView Answer on Stackoverflow
Solution 13 - JavascriptcbdeveloperView Answer on Stackoverflow