Overriding interface property type defined in Typescript d.ts file

260,595

Solution 1

I use a method that first filters the fields and then combines them.

reference 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

 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<T, R> = Omit<T, keyof R> & R;

// before [email protected]
type Modify<T, R> = Pick<T, Exclude<keyof T, keyof R>> & 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

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

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

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;
Share:
260,595

Related videos on Youtube

Abdul23
Author by

Abdul23

Updated on November 04, 2021

Comments

  • Abdul23
    Abdul23 over 2 years

    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?

  • Freewind
    Freewind over 5 years
    It's great to know this. But the problem is it's still not modifying the existing one.
  • aruno
    aruno over 5 years
    Very cool :-) I've done this before with one or two properties with Omit, but this is much cooler :-) I often want to 'extend' a server entity type and change some things to be required or optional on the client.
  • Dawson B
    Dawson B almost 5 years
    This was exactly what I was looking for. It's how I expected the typescript extend to work by default, but alas, this little Omit fixes everything 🙌
  • manuhortet
    manuhortet almost 5 years
    This should be the accepted solution now. Cleanest way to "extend" an interface.
  • mhodges
    mhodges over 4 years
    Extending the interface was exactly what I was looking for, thanks!
  • Dominic
    Dominic over 4 years
    Noob here but you're change from an interface to a type in your example no? Or is there no difference?
  • Qwerty
    Qwerty over 4 years
    @Dominic Good point, I have updated the answer. Two Interfaces with same name can merge. typescriptlang.org/docs/handbook/…
  • Vixson
    Vixson almost 4 years
    Note: You'll need typescript 3.5.3 above to use this.
  • dwoodwardgb
    dwoodwardgb almost 4 years
    This is exactly what I was looking for, I can't thank you enough :D :D :D
  • Toni
    Toni over 3 years
    @dwoodwardgb glad it was useful for someone else :-)
  • Ambrus Tóth
    Ambrus Tóth about 3 years
    This should become a TS feature
  • Steve Moretz
    Steve Moretz about 3 years
    Is it 20201 yet? Lol. You're talking to us from the future.Are there flying cars there?
  • yongming zhuang
    yongming zhuang about 3 years
    Hahaha, I would like to say Yes, cars are flying and people are traveling by jet suit. :-) Thanks for pointing out the typo!!!
  • victor zadorozhnyy
    victor zadorozhnyy almost 3 years
    I'm from the future. It does not work incorrectly extends interface
  • yongming zhuang
    yongming zhuang almost 3 years
    What is your ts version? Did you declare the interface in the d.ts file? @victorzadorozhnyy
  • victor zadorozhnyy
    victor zadorozhnyy almost 3 years
    @yongmingzhuang 4.2 I've try to do it in tsx
  • yongming zhuang
    yongming zhuang almost 3 years
    @victorzadorozhnyy, I am not sure why, but interface overrides seem only work in the d.ts file.
  • Qwerty
    Qwerty almost 3 years
    Cannot find name Obj.
  • Qwerty
    Qwerty almost 3 years
    I was creating a deep modification type myself and I could not make it work without your DeepPartialAny. Otherwise I converged to basically the same solution as you, so I decided to not include my version in my answer, but instead update and improve yours.
  • forresthopkinsa
    forresthopkinsa over 2 years
    This is really hard to follow. Do you think you could make the code a little more verbose?
  • forresthopkinsa
    forresthopkinsa over 2 years
    I was able to get a less robust (but tolerable) version of this working like so
  • Eric Burel
    Eric Burel over 2 years
    Can you still use "B" where "A" is expected? I have 2 types, "ModelShared" and "ModelServer", and a field serverOnly. I want server-only to never exist in ModelShared, so I use {serverOnly: never} => that's excellent for developer experience, they can immediately tell that the field exist, but they should use ModelServer instead. But I still have a lot of functions that expect a generic ModelShared object. However, all listed answers seems to break inheritance: if you try to pass a ModelServer, you get issues, while those types are actually related. I ended up using 3 interfaces.
  • Admin
    Admin over 2 years
    What is Modify here?
  • Qwerty
    Qwerty over 2 years
    @Donnovan It's a custom type, go through the answer again find -> type Modify<T, R> = Omit<T, keyof R> & R;
  • Royer Adames
    Royer Adames about 2 years
    // Omit a single property: type OmitA = Omit<Test, "a">; // Equivalent to: {b: number, c: boolean} // Or, to omit multiple properties: type OmitAB = Omit<Test, "a"|"b">; // Equivalent to: {c: boolean}
  • charles-allen
    charles-allen about 2 years
    If you add & N to your OverrideProps type, I think it will also support new props only in N. Or maybe you could union the keys P in keyof (M & N).
  • Aidin
    Aidin about 2 years
    Can you give me a TypeScript Playground link to what you mean, @charles-allen?
  • charles-allen
    charles-allen about 2 years
    With & N it allows the overriding type to narrow props that weren't already narrowed in the base: typescriptlang.org/play?#code/…
  • Aidin
    Aidin about 2 years
    Got it. Right. Feel free to edit my answer and add this pro-tip. Thanks! :)
  • Marcos Freitas
    Marcos Freitas almost 2 years
    I think it could be more useful if We can still validate the presence of the properties as We were using the T1 interface directly because the purpose is just overriding the type not extending the original interface. Eg.: interface T1 { a: object } // don't works as expected type Modify<T, R> = Omit<T, keyof R> & R; type overridenTypeOnly = Modify<T1, { a: number, b: string }> The result would be that the "a" property has its type changed, but should not accept the "b" property because it isn't in the T1 interface. Any solution for this?