typescript

delete user.password in a typesafe way


I'm trying to delete the password key in a user object in a typesafe way but typescript keeps complaining.

const user = db.findUnique({where: email:input.email});

// returning user without password
// OPTION 1 - eslint disable doesn't remove the yellow squiggly on the password variable
// @eslint-disable-next-line @typescript-eslint/no-unused-vars
const { password, ...userWithoutPassword } = user;
return userWithoutPassword;

// OPTION 2 - Unsafe return of an `any` typed value.eslint@typescript-eslint/no-unsafe-assignment
return omit(user, "password"); // using lodash
// this also leads to the same error
const userWithoutPassword: Omit<User, "password"> = omit(user, "password");
return userWithoutPassword;

// OPTION 3 - The operand of a 'delete' operator must be optional.
// I don't want to change the type definition of User to make password optional. It should be a new type
delete user.password;
return user;

What's the appropriate way to do this?


Solution

  • The return type for lodash's omit (source) should produce a type that is structurally compatible with the desired type Omit<User, "password">. It sounds like there might be a configuration issue in your project if you are seeing any as the return type of the omit function. (Have you installed @types/lodash in your project?) See example in this playground.

    If, for some reason, you want to mutate the existing user object by deleting the password property (instead of creating a new object), you will have to use some type assertions in order to satisfy the compiler because it does not allow for modifying types in-place — see functional predicates (a.k.a. type guards) for more.

    Here's an example:

    TS Playground

    type User = {
      name: string;
      id: string;
      email: string;
      password: string;
      // etc.
    };
    
    function getUserWithoutPassword (): Omit<User, "password"> {
      // const user = db.findUnique({where: email:input.email});
      const user: User = {
        name: "Foo",
        id: "abc123",
        email: "foo@bar.baz",
        password: "1_bad_password",
      };
    
      delete (user as Partial<Pick<User, "password">>).password;
      return user as Omit<User, "password">;
    }
    
    

    delete (user as Partial<Pick<User, "password">>).password;
    //     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    In the statement above, user is being asserted as the type { password?: string }, so the compiler will allow deletion of the optional value.

    return user as Omit<User, "password">;
    //     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    And in the return statement, user is being asserted as a type that represents the result of the previous deletion operation.