I am working with objects I put in and out of databases/actions and sometimes wish to add and remove database/action-specific fields.
For example, I have:
type EntryEvent<EntryLine> = { type: 'entry' } & EntryLine;
Elsewhere I wish to drop the type
member in order to turn an EntryEvent<EntryLine>
back into an EntryLine
. To do that I use a utility function as follows:
export function stripFields<T extends object, K extends keyof T>(
o: T,
fields: K[]
): Omit<T, K> {
const result: Partial<T> = { ...(<object>o) };
for (const field of fields) {
delete result[field];
}
return <Omit<T, K>>result;
}
The trouble is, the resulting object won't be recognized as an EntryLine
.
If I try to assign the following to an EntryLine
type:
stripFields(event, ['type'])
I will get:
Argument of type 'Pick<EntryEvent<EntryLine>, Exclude<keyof EntryLine, "type">>' is not assignable to parameter of type 'EntryLine'.
'Pick<EntryEvent<EntryLine>, Exclude<keyof EntryLine, "type">>' is assignable to the constraint of type 'EntryLine', but 'EntryLine' could be instantiated with a different subtype of constraint 'Pick<object, never>' ts(2345)
As far as I can tell that makes sense, because if the generic type EntryLine
included a type
member, the result of stripFields
would no longer be an EntryLine
(since it would be missing its type
member).
Is there some way I can define constrain the generic type EntryLine
so that it does not allow objects with a type
field? Or is there some other means of working around this?
Update:
The following code reproduces the problem for me:
type EntryEvent<EntryLine> = { type: "entry" } & EntryLine;
type MyEntryLine = {
a: Date;
};
export function stripFields<T extends object, K extends keyof T>(
o: T,
fields: K[]
): Omit<T, K> {
const result: Partial<T> = { ...(<object>o) };
for (const field of fields) {
delete result[field];
}
return <Omit<T, K>>result;
}
type Callback<EntryLine> = (a: EntryLine) => void;
function thingThatTakesAnEvent<EntryLine>(a: EntryEvent<EntryLine>, callback: Callback<EntryLine>) {
callback(stripFields(a, ["type"]));
}
function thingThatTakesAMyEntryLine(a: MyEntryLine) {
console.log(a.a);
}
declare let event: EntryEvent<MyEntryLine>;
thingThatTakesAnEvent(event, thingThatTakesAMyEntryLine);
'Pick< EntryEvent< EntryLine>, Exclude< keyof EntryLine, "type">>' is assignable to the constraint of type 'EntryLine', but 'EntryLine' could be instantiated with a different subtype of constraint '{}'.
Error messages in this form basically mean, that you tried to infer too much from the passed in generic type parameter, whose exact type only the caller/client can know. Typescript does not consider your speculations about its type to be safe anymore.
Concerning your code, it could be translated to this:
function thingThatTakesAnEvent<EntryLine>(
a: EntryEvent<EntryLine>,
callback: Callback<EntryLine>
) {
callback(stripFields(a, ["type"]));
//------ error ------//
}
==> the return type of stripFields
, which is Pick< EntryEvent< EntryLine>, Exclude< keyof EntryLine, "type">>
, is assignable to the constraint EntryLine
(which defaults here to {}
), but what if the caller invoked the function like this: thingThatTakesAnEvent<{foo: "bar"}>
? Inside the function we cannot know that it's {foo: "bar"}
, and we should invoke callback
with EntryLine
, but we did with a specific subtype, which may be not the right one from the caller.
Based on the purpose, you could do the following:
stripFields
Playground : type Callback<EntryLine> = (a: Omit<EntryEvent<EntryLine>, "type">) => void;
thingThatTakesAndEvent
Playground : function thingThatTakesAnEvent<EntryLine>(
a: EntryEvent<EntryLine>,
callback: Callback<EntryLine>
) {
callback((stripFields(a, ["type"]) as unknown) as EntryLine);
}
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.