Reputation: 37464
I have the following situation:
const user: UserObj = User.get(userId);
if ([user.foo, user.bar, user.baz].some((k) => !k))
throw new Error(`Missing fields for user ${userId}`);
const createParams: CreateParams = {
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
phone: user.phone,
foo: user.foo,
bar: user.bar,
baz: user.baz
};
Because foo bar baz
are required in CreateParams
, TS is complaining because those keys are optional keys in UserObj
, however, I believe I've safeguarded against that with my .some
check, yet TS still complains. How can I convince TS that those required keys will be there?
Upvotes: 1
Views: 73
Reputation: 213358
There are a couple ways you can do this. One way is to extract the guard into a function:
interface HasOptional extends UserObj {
foo: string,
bar: string,
baz: string,
}
function hasOptional(user: UserObj): user is HasOptional {
return [user.foo, user.bar, user.baz].every(k => k);
}
if (!hasOptional(user))
throw new Error('missing fields');
Another way is to rewrite the conditional so TypeScript can understand it:
if (!user.foo || !user.bar || !user.baz)
throw new Error('missing fields');
As a minor note, it can be surprising to use “truthiness” to check whether properties exist, because !""
is true, !0
is true, and some others. If you use .some(k => !k)
to check for missing properties, it will throw an exception for both missing properties and empty strings, which may or may not be what you want. Consider whether one of the following tests may be suitable:
// Works if 0/''/false are legal values for foo/bar/baz.
function hasOptional(user: UserObj): user is HasOptional {
return [user.foo, user.bar, user.baz].every(k != null);
}
// Also works if null is a legal value for foo/bar/baz.
function hasOptional(user: UserObj): user is HasOptional {
return [user.foo, user.bar, user.baz].every(k !== undefined);
}
// Works even if undefined is a legal value for foo/bar/baz.
function hasOptional(user: UserObj): user is HasOptional {
return ['foo', 'bar', 'baz'].every(k in user);
}
Upvotes: 3
Reputation: 8459
If typescript does not trust your check, there's no other way I think. Resort to @ts-ignore
or ||
.
//@ts-ignore
foo: user.foo
// or
bar: user.bar || ''
If you don't like that, you can define another type to tell ts that typechecking has been done.
For example:
// interface TypeChecked extends User
type TypeChecked {
foo: string;
bar: boolean;
baz: any;
}
const createParams: CreateParams = {
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
phone: user.phone,
foo: (<TypeChecked> user).foo,
bar: (<TypeChecked> user).bar,
baz: (<TypeChecked> user).baz
};
Upvotes: 1