I Have a method which takes a single parameter which is a union type of obj: QueryResultRow | readonly QueryResultRow[]
. Since this can be either type of QueryResultRow or QueryResultRow array I need a way to determin which is which. I can do that via Array.isArray(obj)
but the typescript compiler does not recognise that after that point it is the single record and throws an error. My method is
protected rowMapper(obj: QueryResultRow | readonly QueryResultRow[])
{
if (Array.isArray(obj))
{
return obj.map((row) => new Contact(row.id, row.name, row.email, row.phone_number, row.creation_date));
}
return new Contact(obj.id, obj.name, obj.email, obj.phone_number, obj.creation_date);
}
The error I get is TS2339: Property 'id' does not exist on type 'QueryResultRow | readonly QueryResultRow[]'. Property 'id' does not exist on type 'readonly QueryResultRow[]'.
because it doesnt know its the single record from the point after the if statement. There is no problem in funcitonality because I can guarantee what it is but I would like the typescript compiler to know.
CodePudding user response:
Notes:
Array.isArray()
does not work forReadonlyArray
- see #17002: Array.isArray type narrows to any[] for ReadonlyArray- you should use an
else
clause
Example code for typescript 4.6.2:
type QueryResultRow = {
id: string
}
function foo(obj: QueryResultRow | Array<QueryResultRow>) {
if (Array.isArray(obj)) {
return obj.map(row => row.id);
} else {
// typescript knows that obj is of type QueryResultRow in the else branch
return obj.id;
}
}
function fooReadonly(obj: QueryResultRow | ReadonlyArray<QueryResultRow>) {
if (Array.isArray(obj)) {
// note: obj is of type any[]
return obj.map(row => row.id);
} else {
/**
* ERROR: Property 'id' does not exist on type 'QueryResultRow | readonly QueryResultRow[]'.
* Property 'id' does not exist on type 'readonly QueryResultRow[]'.
*/
return obj.id;
}
}
The linked issue mentions some workarounds.
I'd just use a simple type-guard in this case:
/**
* type guard is needed until this bug is fixed:
* https://github.com/microsoft/TypeScript/issues/17002
*/
function isNotArray<T>(value: T | ArrayLike<T>): value is T {
return !Array.isArray(value);
}
function fooReadonlyFixed(obj: QueryResultRow | ReadonlyArray<QueryResultRow>) {
if (isNotArray(obj)) {
return obj.id;
} else {
return obj.map(row => row.id);
}
}
function fooFixed(obj: QueryResultRow | Array<QueryResultRow>) {
if (isNotArray(obj)) {
return obj.id;
} else {
return obj.map(row => row.id);
}
}
note, that we have inverted the logic (isNotArray instead of isArray), so that it works correctly for Array<>
and ReadonlyArray<>