When using Array.filter()
I'm not sure how to achieve what I'm describing below.
I don't want to create a new type just for this (but if there's no other way around, that's okay):
interface GPSLocation {
lat: number
lng: number
}
interface House {
address: string
location?: GPSLocation
}
const house01: House = {
address: '123 street'
}
const house02: House = {
address: '123 street',
location: {
lat: 111.111,
lng: 222.222
}
}
const allHouses = [house01, house02]
// Infered by Typescript: const gpsLocationList: (GPSLocation | undefined)[]
// Expected: const gpsLocationList: (GPSLocation)[]
const gpsLocationList = allHouses.filter((house) => house.location !== undefined).map(house => house.location)
CodePudding user response:
Indeed annoying.
Here are two common approaches.
(1) Use as
to force the compiler to treat it as GPSLocation
:
const gpsLocationList = allHouses.filter((house) => house.location !== undefined)
.map(house => house.location as GPSLocation)
(2) Use a function that throws an error if its input is falsy:
function reify<T>(t: T|null|undefined): T {
if (t === null || t === undefined) {
throw new Error(`got a falsy value`)
}
return t
}
const gpsLocationList = allHouses.filter((house) => house.location !== undefined)
.map(house => reify(house.location))
CodePudding user response:
Make your filter callback be a type guard that forces location not to be optional.
I know there's an existing post with a great answer, but since it's not that easy to infer the solution for this case (mapping to an optional subproperty), and I had to do the same thing in the past few days, I will share this here.
const gpsLocationList = allHouses
.filter((house): house is House & {location: GPSLocation} => {
return house.location !== undefined;
})
.map(house => house.location);
I would prefer to split this into a separate declaration for clarity.
type HouseLocationRequired = House & {location: GPSLocation};
const gpsLocationList = allHouses
.filter((house): house is HouseLocationRequired => {
return house.location !== undefined;
})
.map(house => house.location);
If you want to really minimize duplication of types, you can use Required<T>
and Pick<>
type HouseLocationRequired = House & Required<Pick<House, "location">>;
And abstract it one level further:
type RequiredSubProperty<Class, Prop extends keyof Class> = Class & Required<Pick<Class, Prop>>;
const gpsLocationList = allHouses
.filter((house): house is RequiredSubProperty<House, "location"> => {
return house.location !== undefined;
})
.map(house => house.location);
See live example