Jim Englert
Jim Englert

Reputation: 163

Typescript: Remove an attribute from an object safely

I'd like to write a typescript function that takes an object that includes the parameter foo and outputs the same object without the parameter foo. I want to do this all in a typesafe manner. Seems pretty simple, but I'm stumbling. Here is the code I have so far:

interface FooProp {
    foo: string;
}
type WithFooProp<P extends object> = P extends FooProp ? P : never;
type WithoutFooProp<P extends object> = P extends FooProp ? never: P;

function withoutFooProp<P extends object>(input: WithFooProp<P>): WithoutFooProp<P> {
    // How do I get rid of these any's
    const {foo, ...withoutFoo} = input as any;
    return withoutFoo as any;
}

This isn't great because I use any a bunch. I'd sorta expect the code to work as is without any, but TS complains that input isn't an object and can't be destructured. How could I improve this method?

In addition, when I use this function, TS forces me to provide the generic types explicitly. I wish it would infer the types implicitly. Is there any way I could write the function to implicitly grab the parameters.

// Compiles - but I have to specify the props explicitly
withoutFooProp<{foo: string, bar: string}>({
    foo: 'hi',
    bar: 'hi',
});

// This doesn't compile - I wish it did!  How can I make it?
withoutFooProp({
    foo: 'hi',
    bar: 'hi',
});

Thanks in advance!

Upvotes: 2

Views: 2940

Answers (2)

simply good
simply good

Reputation: 1103

You can also use this typesafe function which works as generic and can be used for any object:

export function deleteProperty<TObject, TProp extends keyof TObject>(object: TObject, prop: TProp): Omit<TObject, TProp> {
 const {
    [prop]: toDelete,
    ...newObject
 } = object

 return newObject;
}

Upvotes: 1

Matt McCutchen
Matt McCutchen

Reputation: 30919

You can let the parameter type be simply P, where P is constrained to contain the foo property, so that type inference will work: it will just set P to the argument type. Then use Pick and Exclude to generate the type of the output object containing all properties of P except foo.

interface FooProp {
    foo: string;
}
type WithoutFooProp<P> = Pick<P, Exclude<keyof P, "foo">>;

function withoutFooProp<P extends FooProp>(input: P): WithoutFooProp<P> {
    const {foo, ...withoutFoo} = input as any;
    return withoutFoo;
}

You won't be able to get rid of the any unless/until this suggestion is implemented. I wouldn't worry about it.

This withoutFooProp function should work on any concrete input type and give you a concrete output type. If you are using withoutFooProp on objects of generic types, then you may run into problems because of TypeScript's limited ability to reason about Pick and Exclude, and you may wish to use intersections instead (even though doing so is technically unsound), as demonstrated in this answer in the context of React higher-order components.

Upvotes: 3

Related Questions