Simply like so:
/**
* In `Target`, replace all instances of the type assignable to `Assignable` with the type `Replacement`
*/
export type AssignableTypeReplacer<Target, Assignable, Replacement> = {
[Property in keyof Target]: Target[Property] extends Assignable ? Replacement : Target[Property];
}
interface MyTestInterface {
something: string,
}
type test = AssignableTypeReplacer<MyTestInterface, string, BigNumber>
Becomes:
type test = {
something: BigNumber;
}
Or as an interface:
interface test extends AssignableTypeReplacer<MyTestInterface, string, BigNumber> {}
const blah: test = {
something <-- this is a big number now when hovering over it
}
If we do the following, the typecheck is happy:
interface test extends AssignableTypeReplacer<MyTestInterface, string, BigNumber> {}
const blah: test = {
something: new BigNumber(1)
}
You can combine the two together too, for easier reading:
type MyBigNumberTestInterface = AssignableTypeReplacer<MyTestInterface, string, BigNumber>
interface test extends MyBigNumberTestInterface {}
const blah: test = {
something: new BigNumber(1)
}
If you want to replace by key names, you can do similar if you use the following:
/**
* In `Target`, replace all instances of properties whose keys are in the union `Keys` with the type `Replacement`
*/
export type KeyedTypeReplacer<Target, Keys extends keyof Target, Replacement> = {
[Property in keyof Target]: Property extends Keys ? Replacement : Target[Property];
}
interface MyTestInterface {
something: string,
}
type MyBigNumberTestInterface = KeyedTypeReplacer<MyTestInterface, "something", BigNumber>
interface test extends MyBigNumberTestInterface {}
const blah: test = {
something: new BigNumber(1)
}
If we try to do bad stuff, we get an error:
const blah: test = {
something: "new BigNumber(1)"
}
Type 'string' is not assignable to type 'BigNumber'.ts(2322)
The expected type comes from property 'something' which is declared here on type 'test'
Or as the type:
type MyBigNumberTestType = KeyedTypeReplacer<MyTestInterface, "something", BigNumber>
const blah: MyBigNumberTestType = {
something: "new BigNumber(1)"
}
Type 'string' is not assignable to type 'BigNumber'.ts(2322)
The expected type comes from property 'something' which is declared here on type 'KeyedTypeReplacer<MyTestInterface, "something", BigNumber>'
We can combine both to achieve maximum effect:
/**
* In `Target`, replace all instances of properties whose keys are in the union `Keys` if they are assignable to `Assignable` with the type `Replacement`
*/
export type AssignableKeyedTypeReplacer<Target, Keys extends keyof Target, Assignable, Replacement> =
KeyedTypeReplacer<Target, Keys, Replacement>
& AssignableTypeReplacer<Target, Assignable, Replacement>
;
interface MyTestInterface {
something: string,
}
type MyBigNumberTestInterface = AssignableKeyedTypeReplacer<MyTestInterface, "something", number, BigNumber>
const blah: MyBigNumberTestInterface = {
something: new BigNumber(1)
}
Type 'BigNumber' is not assignable to type 'BigNumber & string'.
Type 'BigNumber' is not assignable to type 'string'.ts(2322)
The expected type comes from property 'something' which is declared here on type 'MyBigNumberTestInterface'
Whoops! Notice I'm trying to say something, which was a string in Target, can be replaced to a BigNumber if it is a number, but in this case, our original object was a string, but we said it had to be a number we were replacing...
If I change it back to a string... its happy again:
type MyBigNumberTestInterface = AssignableKeyedTypeReplacer<MyTestInterface, "something", string, BigNumber>
const blah: MyBigNumberTestInterface = {
something: new BigNumber(1)
}