Generic Typeguard

71 Views Asked by At

So at work we use these the types which create the base of of our Geometries:

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 is that we end up having to create separate typeguard for each Geometry:

export const isPointGeometry = (g: Geometry): g is Point => {
  return (g as Point).type === 'Point';
};

I tried to create a generic typeguard by writing:

export function isGeometry<T extends Geometry, G extends GeoJsonObject['type']>(feature: T, geometry: G): feature is T {
  return feature && feature && feature.type === geometry;
}

But to no avail. as i still get an issue with? Anyone got an idea?

export function coordinatesFor(f: Feature<Point | MultiPoint>) {
  if (isGeometry(f.geometry, 'Point')) {
   return f.geometry.coordinates ?? []; // Expected Feature<Point> but got Feature<Point | MultiPoint>
  }
  return [];
 }
1

There are 1 best solutions below

3
darbaidze2020 On

Try this way:

function isGeometry<T extends Geometry, G extends T['type'] = Geometry['type']>(
  feature: T,
  geometry: G,
): feature is Extract<T, { type: G }> {
  return feature && feature && feature.type === geometry;
}

Here we do two things:

  • We infer type G based on T argument in order to make sure, that G is always compatible with given T and make it impossible to call the function with wrong combination of arguments, such as: isGeometry(somePoint, 'Polygon').

  • With Extract<T, { type: G }> in return type we pick those Geometry union members, that are compatible to (the geometry we are looking for){ type: G }.