I am using a function from a package which returns a value as any;
// node_modules/reflect-metadata/index.d.ts
function getMetadata(metadataKey: any, target: Object): any;
Like so;
const example = getMetadata('string', myObj);
I am also using eslint for typescript which enforces safe assignments on the default configuration. I get an error for the above line because the type signature of getMetadata returns and assigns the type any to example.
Unsafe assignment of an
anyvalue
I don’t really want to disable this rule make an exception for this line. How do can I narrow such library function Types manually?
>Solution :
Probably by using wrappers, rather than overloads. What you do in the wrappers depends on how typesafe you want the code to be.
You could just assume the data will be the way you expect it, in which case you only need one wrapper, but the opportunities for error are numerous:
// Lots of opportunities for error here
import { getMetadata as realGetMetadata } from "wherever";
// ...
export function getMetadata<T>(key: string): T {
return realGetMetadata(key) as T;
}
(I’ve left off the target parameter there and below because I don’t know what it’s for, but it’s largely tangential to the discussion.)
But again, that’s not very typesafe, you’re just asserting that you have the right kind of value. Ideally, at least in development and staging, you want to prove that the assertion is correct and raise an error if it isn’t. One way to do that is to have multiple functions:
import { getMetadata as realGetMetadata } from "wherever";
// ...
export function getMetadataString(key: string) {
const value = realGetMetadata(key);
if (typeof value !== "string") {
throw new ValidationError(/*...*/);
}
return value;
}
export function getMetadataNumber(key: string) {
const value = realGetMetadata(key);
if (typeof value !== "number") {
throw new ValidationError(/*...*/);
}
return value;
}
// ...
You end up with a bunch of functions (particularly if there are lots of different object formats to validate), but you’re ensuring type safety.
To avoid having quite so many functions, you could have validator functions you pass in along with the key, but unless you have a means of aggregating them in some way, you end up with just as many functions (plus the basic call) if you need to validate object or tuple structures.
To cope with that, you might use a library that’s designed to validate or parse data in a typesafe way. One such library is zod, but while I’ll use it as an example, it’s not the only game in town and you could even roll your own if you wanted. (I have no affiliation with zod.)
Here’s a zod example:
import { z } from "zod";
import { getMetadata as realGetMetadata } from "wherever";
// ...
export function getMetadata<
SchemaType extends z.ZodTypeAny,
ResultType extends ReturnType<SchemaType["parse"]>
>(key: string, schema: SchemaType): ResultType {
const rawValue = realGetMetadata(key);
const value = schema.parse(rawValue);
return value;
}
Then for example:
// To get a string:
const str = getMetadata("foo", z.string());
// To get a number:
const num = getMetadata("foo", z.number());
// To get an object with `name` and `age` properties:
const num = getMetadata("foo", z.object({
name: z.string(),
age: z.number(),
}));
// ...
As you can see from the code above, zod objects have a parse function whose return type matches the type of what you’ve described. So in the above, the return type is string, then number, then {name: string; age: number; }. Once nice thing about this approach is that the code doing the metdata retrieval defines what it’s expecting to see. (Either inline as above, or by creating and reusing schema objects.)
If you wanted to avoid the runtime cost of the validation, you could bypass it in production (branch based on environment and just assign rawValue to value with a type assertion). In some cases that may be suitable, though it depends on what library you use (if any) and what features you use. For example, most (I assume) will go beyond simply "is it a string?" or "is it a number?" and offer "is it at least N characters long?" or "is it between X and Y?", which you’d probably want in production as well.