Reputation: 14435
Let's have the following two interfaces:
interface A {
foo: string;
bar: number;
}
interface B {
foo: string;
}
How can I type-safely convert an object that conforms to A to one that conforms to B?
This doesn't work:
const a = { foo: "baz", bar: 42 }
const b: B = {...a}
delete b.bar // <=== cannot remove "bar" because B has no "bar"
I could do it without type-safety:
const a = { foo: "baz", bar: 42 }
const temp: any = {...a}
delete temp.bar
const b: B = temp
I assume this works:
const a = { foo: "baz", bar: 42 }
const b: B = {
...a,
bar: undefined
}
But this is not what I want, as it assigns undefined
to b.bar
, which could lead to problems (e.g. Firestore won't accept it as input, as it does not allow undefined values).
Is there a type-safe way to do this?
Upvotes: 8
Views: 7144
Reputation: 1932
You could create a temporary constant and cast this to Partial<A>
(playground):
const a: A = { foo: "baz", bar: 42 }
const copy = {...a}
const b: B = copy
delete (copy as Partial<A>).bar
Alternatively you could create a function like this (playground):
const reduceInterfaceTo = <Child extends Parent, Parent extends object>(
obj: Child,
keys: Exclude<keyof Child, keyof Parent>[]
): Parent => {
const copy = { ...obj }
for (const key of keys)
delete copy[key]
return copy
}
const a: A = { foo: "baz", bar: 42 }
const b: B = reduceInterfaceTo<A, B>(a, ['bar'])
Interestingly, this function (apparently) reaches a limit of Typescript, else we would also need to cast it as Partial
. (in case you're interested how to abuse the limit, try to change the return type to Child
to produce unsafe results)
Also note, that this functions accepts e.g. an empty list as keys
parameter, so it is still up to the developer to ensure all keys that should be deleted are passed. However, it ensures that the result conforms to the Parent
type by only deleting properties that are special to Child
Upvotes: 1
Reputation: 1999
What you are wanting was added in TypeScript 3.5+. It is called the Omit helper type.
Super simple, in your case it will be:
type A = {
foo: string;
bar: number;
}
type B = Omit<A, "bar">
To use this functionality with interfaces, you can simply throw in the extends
keyword:
interface A {
foo: string;
bar: number;
}
interface B extends Omit<A, "bar"> {};
// No error
const test: B = {
foo: "hello"
};
This way you do not need extra logic to do something that can be done completely in TypeScript type system.
You can test this out in the playground here
Upvotes: 1
Reputation: 14171
One thing that you can do is destructuring assigment
. Something similar to this:
const a = { foo: "baz", bar: 42 }
const {foo, ...b}= a
b
with contain all properties except foo
. TS
will be able to use infer the type for b
as being compatible with B
.
Upvotes: 11