So I have the following very simple User
interface and getUserById()
method that retrieves a type-safe user that includes only the specified fields/properties (what is usually called a projection):
interface User {
_id: string,
username: string,
foo: string
}
function getUserById<Field extends keyof User>(
id: string,
fields: Field[]
): Pick<User, Field> {
// In real-life an object with only the specified fields/properties is returned
// and the id and fields parameters are both used as part of this process
return {} as User;
}
This is working perfectly as demonstrated in the usage bellow, because user1
is fully type-safe and accessing user1.foo
will raise a Typescript error:
const user1 = getUserById('myId', ['_id', 'username']);
console.log(user1); // user1 is correctly typed as Pick<User, '_id' | 'username'>
console.log(user1.foo); // We get the error: Property 'foo' does not exist. Great!
However now I want to do what I thought should be a fairly easy thing. As I use the ['_id', 'username']
parameter a lot of times, I want to create a DEFAULT_USER_FIELDS
constant for it so that I can re-use it everywhere:
const DEFAULT_USER_FIELDS: (keyof User)[] = ['_id', 'username'];
const user2 = getUserById('myId', DEFAULT_USER_FIELDS);
console.log(user2); // user2 is INCORRECTLY typed as Pick<User, keyof User>
console.log(user2.foo); // We do NOT get any error. This is bad!
However as you can see, with this simple change, the type safety of user2
is lost, and now I do not get any error when accessing user2.foo
, which is really bad. How can I solve this? I have tried countless things for hours and haven't really found a working solution.
Edit: Code Sandbox with exactly the same code where Typescript errors can be seen and played with.