I have a Car
class, which defines property for the model
of the car.
There are just 3 models possible: 'ModelT', 'ModelQ' and 'ModelX'. So I have decided to define a Model type such as:
type Model = 'ModelT' | 'ModelQ' | 'ModelX';
and the Car constructor method as
class Car {
constructor(model: Model) {
this.model = model;
}
}
There is also a remote service which returns the type of car I should buy. If I use such service, my code looks like
const model = getModelFromRemoteService();
const car = new Car(model);
Which is the best way to enforce a logic to check at runtime that the model returned by the remote service is actually one of those specified in the type Model
definition?
It is not possible to start with a type/interface and get runtime behavior from it. The type system in TypeScript exists only at the time you are writing the program. It is completely erased from the emitted JavaScript that is executed at runtime.
Luckily, you can do the reverse: start with an object that exists at runtime and get the TypeScript compiler to infer an analogous type for it. In your case I'd suggest starting with an array of the values you want to check against, and proceed as described here:
// helper function needed before TS3.4 to get a tuple of string literals
// instead of just string[]
const stringTuple = <T extends string[]>(...args: T) => args;
const models = stringTuple('ModelT', 'ModelQ', 'ModelX');
// inferred type of models is ['ModelT', 'ModelQ', 'ModelX'];
// in TS3.4+, const models = ['ModelT', 'ModelQ', 'ModelX'] as const;
type Model = typeof models[number];
// inferred type of Model is 'ModelT' | 'ModelQ' | 'ModelX'
Now you have your Model
type back again, and you also have the models
array value you can use to create a type guard for it:
function isModel(x: any): x is Model {
return models.indexOf(x) >= 0;
// or return models.includes(x) for ES2016+
}
And now you can use it like this:
class Car {
model: Model;
constructor(model: Model) {
this.model = model;
}
}
// assume this returns a string
declare function getModelFromRemoteService(): string;
// wrap getModelFromRemoteService so that it returns a Model
// or throws a runtime error
function ensureModelFromRemoteService(): Model {
const model = getModelFromRemoteService();
if (isModel(model)) return model;
throw new Error("THAT REMOTE SERVICE LIED TO ME");
}
const model = ensureModelFromRemoteService();
const car = new Car(model); // works now
Okay, hope that helps. Good luck!