Types aren't available at runtime.
In order to make things DRY, helper definition object can be used:
const Point2DDefinition = { x: 1, y: 1 };
type Point2D = typeof Point2DDefinition;
const point: Point2D = Object.entries(response)
.filter(([k]) => Object.keys(Point2DDefinition).includes(k))
.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {} as Point2D);
Because definition object relies on inferred type, it has certain limitations, e.g. intersection or union types cannot be used (a value cannot be a number and a string at the same time).
Notice that this code doesn't contain checks that point
has all properties from Point2D
, so technically it's more like point: Partial<Point2D>
. It also doesn't check that values have same types as in definition.
Both checks can be additionally provided for type safety at runtime.
Alternatively, Point2D
can be converted to a class that takes care of omitting unnecessary properties on construction.
Properties should be listed explicitly:
class Point2D {
x: number;
y: number;
constructor({ x, y }: Point2D) {
this.x = x;
this.y = y;
}
}
Validation can be optionally added to class constructor for type safety at runtime.
A workaround for not listing properties explicitly would to combine a class with helper definition object to iterate over object properties. Declaration merging can be used to assert that Point2D
class has all properties that are listed in Point2DDefinition
:
type TPoint2D = typeof Point2DDefinition;
interface Point2D extends TPoint2D {};
class Point2D {
constructor(point: Point2D) {
for (const k of Object.keys(Point2DDefinition)) {
// runtime check for value types can also be added
if (k in point) {
this[k] = point[k];
} else {
throw new TypeError();
}
}
}
}
The important thing is that the reponse can have any number of foreign properties, so I cannot use object destructuring with the rest operator.
Object destructuring results in WET but type-safe (at compilation time) code and certainly can be used for this purpose, for example:
const point: Point2D = (({ x, y }) => ({ x, y }))(response as Point2D);
It doesn't need the ...rest
of the properties because they are supposed to be discarded.