jamis0n
jamis0n

Reputation: 3800

Typescript function to omit certain keys and add others from it's argument

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

Answers (1)

satanTime
satanTime

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

Related Questions