so i've got some issues, at work we use these types:
export type GeoJSON = Geometry | Feature;
export type GeoJsonTypes = GeoJSON['type'];
export interface GeoJsonObject { type: GeoJsonTypes; }
export interface Point extends GeoJsonObject {
type: 'Point';
coordinates: number[];
}
export interface MultiPoint extends GeoJsonObject {
type: "MultiPoint",
coordinates: number[][];
}
export type Geometry =
| Point
| MultiPoint
export interface Feature<G extends Geometry = Geometry>
extends GeoJsonObject {
type: 'Feature';
geometry: G;
}
The issue that we're having is that we end up having to create a Typeguard for each Feature at the callsites when we have Feature<Geometry> that we want to narrow.
export function isPolygon(f: Feature): f is Feature<Polygon> {
return f && f.geometry && f.geometry.type === 'Polygon';
}
I want to make a generic typeguard, i used Generic Typeguard as base for my thinking but i'm stuck.
I know that i can use the Extract<Geometry, {type: U}> to get my type such as this example.
export function isFeature<T extends Feature<Geometry>, U extends T['geometry']['type']>(
feature: T,
type: U
): feature is Feature<Extract<Geometry, { type: U }>> {
return feature && feature.geometry && feature.geometry.type === type;
}
The problem that i'm having is that if i constrain T to be of type Feature, i have no use for the T variable in the expression, it gives me an error saying A type Predicate's type must be assignable to it's parameter type, If i constrain it to Geometry, then i can't use Feature<Extract<T, {type: U}>> Cause T could be instantiated with an arbitrary type which could be unrelated, is there any way to have the generic typeguard for a feature?
The issue is that in the type guard you type
featureasTand you are checking whetherfeatureis something else other thanT, which is logically impossible to convertTtoFeature<Extract<Geometry, { type: U }>>. Instead, you should only acceptUand keep thefeaturenongeneric, justunknown.This will cause many repetitive checks and to avoid them we can create a generic
isObjecttype guard that would turn any object typeTtoPartial<Record<keyof T, unknown>>. The reason to turn properties tounknownand make the whole object partial is to make it as type-safe as possible:Next, we can extract a type guard for generic features, ideally you would also include the
geometry.coordinatesinto checking:Finally, our
isFeaturewill look like this:Usage:
playground