Reputation: 5445
I have a variable with the following type:
const data: { error: string; } | { property1: string; };
(It's the response of an external API, so I can't change it.)
I now want to check which of the two possible types data
is. I tried this:
if (typeof data.error !== 'undefined') { }
Unfortunately, TypeScript then complains that
Property `error` does not exist on type `{ property1: string }`.
How do I get TypeScript to know which of the possible types it is?
Upvotes: 1
Views: 48
Reputation: 330501
This is precisely why TypeScript has user-defined type guards. You can tell TypeScript how to distinguish between the two consitutents of the union:
function dataHasError(data: { error: string; } | { property1: string; }): data is { error: string; } {
return 'error' in data;
}
And then it will narrow types the way you expect inside if
/else
clauses:
const data: { error: string; } | { property1: string; } = /* API response */;
if (dataHasError(data)) {
console.log(data.error); // okay
} else {
console.log(data.property1); // okay
}
If you often find yourself needing to do this type narrowing by checking for the presence of a key, you can put the following type guard in your library:
function hasKey<K extends string>(obj: any, key: K): obj is Record<K, any> {
return key in obj;
}
Then the above can be written like this:
if (hasKey(data, 'error')) {
console.log(data.error); // okay
} else {
console.log(data.property1); // okay
}
Relevant note: there is a suggestion for TypeScript to automatically interpret key in obj
as a type guard, so that the following would work with no errors:
if ('error' in data)) {
console.log(data.error); // would be okay, currently error
} else {
console.log(data.property1); // would be okay, currently error
}
Currently (as of TypeScript v2.5) that is not part of the language, so a user-defined type guard is the best answer for now.
Hope that helps; good luck!
Upvotes: 3
Reputation: 5445
This is the workaround I'm currently using. As a workaround, it's fine, but I'd rather use TypeScript's type system properly:
const data: any = /* API response */;
if (data.error) {
const errorData: { error: string; } = data;
// Do whatever
}
const actualData: { property1: string; } = data;
Upvotes: 0
Reputation: 6396
One simple way I can think of is to use one type with optional properties instead of union types. Like this:
interface ApiResponse = {
error?: string;
property1?: string;
}
const errorData: ApiResponse = { error: 'error data' };
const okData: ApiResponse = { property1: 'ok data' };
console.log(typeof errorData.error !== 'undefined'); // true
console.log(typeof okData.error !== 'undefined'); // false
// or even cleaner:
console.log(errorData.error); // truthy
console.log(okData.error); // falsey
console.log(!!errorData.error); // true
console.log(!!okData.error); // false
Upvotes: 0