Reputation: 3800
I'm writing a function whose job it is to take a raw api response and transform it into a shape that is more easily consumable in my app (let's call it a "pretty" response).
The code below is valid according to the TS compiler, but I want it to complain that I haven't removed the metadata
key from the raw response before returning, as metadata
exists in RawResponse
but not in PrettyResponse
.
I thought that my Optionalize
type would do this for me, but it doesn't seem to be working.
What am I misunderstanding here?
Here's a TS Playground link to illustrate as well.
// Omit from T the keys in common with K
type Optionalize<T extends K, K> = Omit<T, keyof K>;
interface RawResponse {
data: [];
metadata: Object;
}
type PrettyResponse<T extends RawResponse> = Optionalize<T, RawResponse> & {
data: [];
version: string;
};
/**
* Take a raw api response and transform it into a pretty one.
* All raw responses have at least the keys of RawResponse.
*/
function parseResponse<T extends RawResponse>(response: T): PrettyResponse<T> {
const result = Object.assign({}, response, {
version: '123'
});
// Shouldn't I have to delete result.metadata before returning?
return result;
}
And an example of consuming it:
interface MyResponse {
data: [];
name: string;
category: string;
other?: number;
metadata: Object;
}
const response: MyResponse = {
data: [],
name: 'a',
category: 'blah',
metadata: {}
}
const myPrettyResponse: PrettyResponse<MyResponse> = parseResponse<MyResponse>(response);
Note: My colleague pointed out that this may be due to Typescript's use of structural subtyping, but I would still expect the explicit return definition of Optionalize<T, RawResponse>
to remove the metadata
key.
Upvotes: 5
Views: 3594
Reputation: 13539
I think you need to add metadata: never
to MyResponse
, then it will always complain to delete it (set to undefined).
interface RawResponse {
data: [];
metadata: Object;
}
type BannedKeys = 'metadata';
type PrettyResponse<T> = Omit<T, BannedKeys> & Record<BannedKeys, never> & {
version: string;
}
function parseResponse<T extends RawResponse>(response: T): PrettyResponse<T> {
const updatedResponse: PrettyResponse<T> = {
...response,
version: '123',
metadata: undefined, // if you comment it - it will ask to delete it.
}
return updatedResponse;
}
interface MyResponse {
data: [];
name: string;
category: string;
other?: number;
metadata: Object;
}
const response: MyResponse = {
data: [],
name: 'a',
category: 'blah',
metadata: {}
}
const myPrettyResponse: PrettyResponse<MyResponse> = parseResponse<MyResponse>(response);
myPrettyResponse.metadata // is undefined.
Upvotes: 3