简体   繁体   中英

Overriding interface property type defined in Typescript d.ts file

Is there a way to change the type of interface property defined in a *.d.ts in typescript?

for example: An interface in xdts 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?

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
}
 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 typescript@3.5 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 .

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'.

The short answer for lazy people like me:

type Overrided = Omit<YourInterface, 'overrideField'> & { overrideField: <type> }; 
interface Overrided extends Omit<YourInterface, 'overrideField'> {
  overrideField: <type>
}

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;

Omit the property when extending the interface:

interface A {
  a: number;
  b: number;
}

interface B extends Omit<A, 'a'> {
  a: boolean;
}

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
}

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 .

eg

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 string | number in our case. So, B would become {x: number; } {x: number; } and accepts any extra property with the type of number | string | boolean number | string | boolean 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!

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

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

Override as Alias Type

You can use this type alias:

type Override<T, K extends { [P in keyof T]: any } | string> =
  K extends string
    ? Omit<T, K>
    : Omit<T, keyof K> & K;

and use alike below syntax:

Global Interface

interface IFirst {
    username: string;
}

Interface by override the just name

interface ISecond extends Override<IFirst, 'username'> {
    username: number;
}

Type alias override

type IThird = Override<IFirst, { username: boolean }>;

EDIT:

I'm tried to add this alias type as build-in type in typescript by send issue as proposal to the Typescript Repo

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 Exclude property from type .

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 for overwriting two or more properties of an interface:

 interface Original { a: string; b: string; c: string; } interface Modified extends Omit<Original, 'a' | 'b'> { a?: string; // make it optional b: boolean; // make it boolean d: number; // add another proeprty }

From TypeScript documentation

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

扩展Qwerty 的Modify 实用程序类型解决方案以将R的键限制为T中存在的键并添加 IntelliSense

export type Modify<T, R extends Partial<Record<keyof T, any>>> = Omit<T, keyof R> & R;

Based on ZSkycat's excellent answer , you can create an abstracted Override generic type that is handy to use and explains clearly the intent of the code.

type Override<T, K extends keyof T, N> = Omit<T, K> & { [K1 in K]: N };

where:

  • T = existing type
  • K = key of type you wish to override
  • N = new type for key of existing type to override

Example usage:

type GraphQLCodegenConfig = Override<CodegenConfig, 'schema', DocumentNode>;

Create A modifier Type

type Modify<T, R extends {[P in keyof T]:any} > = Omit<T, keyof R> & R;

and you can

interface ModifiedInterface extends Modify<OriginalType, {
  a: number;
  b: number;
}> {}

it will give you a type autocomplete

Deep modification v3

* note, version 2 is in the history of this answer .

interface Original {
  a: {
    a: string
    b: { a: string }
    c: string
    d: string         // <- keep this one 
  }
}

interface Overrides {
  a: {
    a: { a: number }  // <- overwrite string with object
    b: number         // <- overwrite object with number
    c: number         // <- overwrite string with number
    e: number         // <- new property
  }
}

type ModifiedType = ModifyDeep<Original, Overrides>
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}
Result
const example: ModifiedType = {
  a: {
    a: { a: number },
    b: number,
    c: number,
    d: string,
    e: number,
  }
}
The code
type ModifyDeep<A, B extends DeepPartialAny<A>> = {
  [K in keyof A | keyof B]:          // For all keys in A and B:
    K extends keyof A                // ───┐
      ? K extends keyof B            // ───┼─ key K exists in both A and B
        ? A[K] extends AnyObject     //    │  ┴──┐
          ? B[K] extends AnyObject   //    │  ───┼─ both A and B are objects
            ? ModifyDeep<A[K], B[K]> //    │     │  └─── We need to go deeper (recursively)
            : B[K]                   //    │     ├─ B is a primitive 🠆 use B as the final type (new type)
          : B[K]                     //    │     └─ A is a primitive 🠆 use B as the final type (new type)  
        : A[K]                       //    ├─ key only exists in A 🠆 use A as the final type (original type)   
      : B[K]                         //    └─ key only exists in B 🠆 use B as the final type (new type)
}

type AnyObject = Record<string, any>

// This type is here only for some intellisense for the overrides object
type DeepPartialAny<T> = {
  /** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
  [P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any
}

*Note, type DeepPartialAny is there just for type hints, but it's not perfect. Technically, the logic of the ModifyDeep type allows to replace leaf nodes {a: string} with objects {a: {b: ... }} and vice versa, but DeepPartialAny will complain when overriding an object with a flat primitive with an error such as this

Type 'number' has no properties in common with type 'DeepPartialAny<{ a: string; }>'

However, you can safely ignore the error (with /// @ts-ignore or remove extends DeepPartialAny constraint altogether. The resulting type is computed correctly anyway.

example

TypeScript Playground

type ModifyDeep<A, B extends DeepPartialAny<A>> = {
  [K in keyof A | keyof B]:
    K extends keyof A
      ? K extends keyof B
        ? A[K] extends AnyObject
          ? B[K] extends AnyObject
            ? ModifyDeep<A[K], B[K]>
            : B[K]
          : B[K]
        : A[K]
      : B[K]
}

type AnyObject = Record<string, any>

type DeepPartialAny<T> = {
  /** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
  [P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any
}


interface Original {
  a: {
    a: string
    b: { a: string }
    c: { a: string }
  }
  b: string
  c: { a: string }
}

interface Overrides {
  a: {
    a: { a: number }  // <- overwrite string with object
    b: number         // <- overwrite object with number
    c: { b: number }  // <- add new child property
    d: number         // <- new primitive property
  }
  d: { a: number }    // <- new object property
}

//@ts-ignore // overriding an object with a flat value raises an error although the resulting type is calculated correctly
type ModifiedType = ModifyDeep<Original, Overrides>
//@ts-ignore
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}


// Try modifying the properties here to prove that the type is working
const t: ModifiedType = {
  a: {
    a: { a: 0 },
    b: 0,
    c: { a: '', b: 0},
    d: 0,
  },
  b: '',
  c: { a: '' },
  d: { a: 0 },
}

Better solution would be to use below Modified type(pun intended) of this answer

export type Modify<T, R extends Partial<T>> = Omit<T, keyof R> & R;

This will also check that keys you are overriding is also present in the original interface and hence make sure that if original interface changes the name then you will get the compile time error and you also have to change the name.

Explanation:

Take the following example.

interface OriginalInterface {
    id: string
}

and modified type is as below

interface ModifiedInterface {
    id: number
}

Now, let say in future, OriginalInterface 's id gets renamed to uId then using my type utility you will get the error as below

interface ModifiedInterface {
    id: number // Type '{ geo_point1: GeoPoint | null; }' has no properties in common with type 'Partial<Address>'.ts(2559)
}

Try this:

type Override<T extends object, K extends { [P in keyof T]?: any }> = Omit<T, keyof K> & K;

Usage:

type TransformedArticle = Override<Article, { id: string }>;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM