I'm trying to setup a generic function that maybe removes a field in a generic type.
type MaybeID = {id?: string};
withoutID<K>(model: K & MaybeID): K {
if (model.id) {
const { id, ...data } = model;
// Type of data is Omit<K & MaybeID, "id">
return data; // Error here
}
return model;
}
I get the error:
Type 'Omit<K & MaybeID, "id">' is not assignable to type 'K'. 'K' could be instantiated with an arbitrary type which could be unrelated to 'Omit<K & MaybeID, "id">'.
If I maybe add the id
property but then remove it, do I not get K
back? Thanks for your help.
CodePudding user response:
do I not get
K
back?
No, you don't — and here's why:
Note that I'll use a type assertion on the returned value in your function to override and suppress the compiler diagnostic — in order to illustrate why it's not what you expect:
type MaybeID = { id?: string };
type ModelConstraint = Record<PropertyKey, unknown> & MaybeID;
function withoutID <K>(model: K & MaybeID): K {
if (model.id) {
const { id, ...data } = model;
return data as K;
}
return model;
}
const model = {
id: '',
strProp: 'hello world',
} satisfies ModelConstraint;
const result = withoutID(model);
//^? const result: { id: string; strProp: string }
console.log('id' in result, result.id); // true ""
The example above uses your function's implementation code. You can see that, when an empty string is provided, the result of the conditional check if (model.id)
is false
(see runtime type coercion, if...else
, truthy, and falsy).
This results in the effect that the code in the corresponding block is never executed, and the object is returned as-is: including the id
property and empty string value.
Instead of using a conditional that coerces the type of the value, you can use the in
operator to determine if the property exists in the object (regardless of the type of its value): this better represents the intent of your code.
It then becomes self-evident why Omit<K, 'id'>
is suggested: the omission of the id
property in the returned value is the type that is returned from the function (because the implementation code ensures that it isn't there — see Omit<Type, Keys>
in the TS handbook):
type MaybeID = { id?: string };
type ModelConstraint = Record<PropertyKey, unknown> & MaybeID;
function withoutID <K extends ModelConstraint>(model: K): Omit<K, 'id'> {
if ('id' in model) {
const { id, ...data } = model;
return data;
}
return model;
}
const model = {
id: '',
strProp: 'hello world',
} satisfies ModelConstraint;
const result = withoutID(model);
//^? const result: { strProp: string }
console.log('id' in result, (result as any).id); // false undefined