I have a method, that should accepts any object, as long as all its fields are strings or numbers
I made this, which works great with duck typing
sta
T
's constraint can refer to T
(with some restrictions), so you can use a mapped type to generate a constraint that has the same fields as T
:
function interpolateParams<T extends {[P in keyof T] : string | number}>(
route: string,
params: T) : string { /*...*/ }
Beware that trickery with circular type parameter constraints can sometimes cause problems, though this scenario will likely be OK.
Here is final version, thanks Matt for hint
static interpolateParams(
route: string,
params: {[key: string] : string | number}) : string {
const parts = route
.split("/")
.map(part => {
const match = part.match(/:([a-zA-Z09]*)\??/);
if (match) {
if (!params[match[1]]) {
if (part.endsWith("?")) {
return null;
}
console.error("route argument was not provided", route, match[1]);
return part;
}
return params[match[1]];
}
else {
return part;
}
}).filter(p => p && p != "");
return "/" + parts.join("/");
}
static formatRoute<T extends {[P in keyof T] : string | number}>(
route: string,
params: T
) : string {
return interpolateParams(route, params);
}
If you want interpolateParams("/api/:method/:value", {method: "abc", value: 10});
to typecheck you can't. This is because you cannot infer anything about "/api/:method/:value"
to give you a method,value
signature.
Write functions that take method
and value
and use it to power both the config and the use.
E.g. this is the strategy I use with takeme.
// Define
export const links = {
profile: (id: string) => `/profile/${id}`
}
// Use in Configure
links.profile(':profileId')
// Use in Navigate / a tags
links.profile(user.id)