I have created a union type:
type RequestParameterType = number | string | boolean | Array<number>;
and I have a class which is a key/value pair holding that union type:
class RequestParameter
{
constructor(name: string, value: RequestParameterType)
{
this.Name = name;
this.Value = value;
}
public Name: string;
public Value: RequestParameterType;
}
I can then make an array of that RequestParameter to hold keys/values:
let parameters: Array<RequestParameter> = new Array<RequestParameter>();
parameters.push(new RequestParameter("one", 1));
parameters.push(new RequestParameter("two", "param2"));
with the idea being that I can write a GetParameter
function to return typed values from that array, which in practice I'd probably use like this:
// should return number type, with value 1
let numberParam: number | undefined = this.GetParameter<number>("one", parameters);
// should return string type, with value "param2"
let stringParam: string | undefined = this.GetParameter<string>("two", parameters);
// should return undefined, because param named 'two' is not number type
let undefinedParam: number | undefined = this.GetParameter<number>("two", parameters);
However I'm having an issue with my function to get typed parameters, because I don't know how to check the generic type matches the value type:
function GetParameter<T extends RequestParameterType>(parameterName: string, parameters: Array<RequestParameter>): T | undefined
{
let result: T | undefined = undefined;
for (let parameter of parameters)
{
// Type check fails: 'T' only refers to a type, but is being used as a value here.
if (parameter.Name === parameterName && parameter.Value instanceof T )
{
// Possibly an issue here too:
// Type 'RequestParameterType' is not assignable to type 'T | undefined'.
// Type 'string' is not assignable to type 'T | undefined'.
result = parameter.Value;
}
}
return result;
}
I believe I may need to write a typeguard function, but I'm struggling in the same way to check the generic type when writing the typeguard. Is this going to be possible to resolve?
Here is the example : in the Playground
TypeScript compiles to JavaScript, which is what actually gets run. The type T
and its specification like <number>
or <string>
will be erased upon compilation, so there's nothing called T
at runtime to use. The instanceof
operator specifically only works when checking against class constructor functions, and since your possible T
values are mostly primitives like string
and boolean
, you wouldn't want to use instanceof
anyway ("foo" instanceof String
is false
).
Instead, you will probably need to pass a type guard function into GetParameter()
as an argument, since such a function will exist at runtime.
That is, you could change GetParameter()
to
function GetParameter<T extends RequestParameterType>(
parameterName: string,
parameters: Array<RequestParameter>,
guard: (x: RequestParameterType) => x is T // new param
): T | undefined {
let result: T | undefined = undefined;
for (let parameter of parameters) {
// new check using guard() instead of instanceof
if (parameter.Name === parameterName && guard(parameter.Value)) {
result = parameter.Value; // no error
}
}
return result;
}
where guard()
has to be a function which can take an object of some RequestParameterType
and narrow it to T
. Here is a set of those you can use:
const guards = {
number: (x: RequestParameterType): x is number => typeof x === "number",
string: (x: RequestParameterType): x is string => typeof x === "string",
boolean: (x: RequestParameterType): x is boolean => typeof x === "boolean",
// the only array matching RequestParameterType is number[], so we can
// just check to see if x is an array without needing to inspect elements
numberArray: (x: RequestParameterType): x is number[] => Array.isArray(x)
};
And then you can call GetParameter()
like this:
let numberParam = GetParameter("one", parameters, guards.number);
console.log(numberParam); // 1
let stringParam = GetParameter("two", parameters, guards.string);
console.log(stringParam); // param2
let undefinedParam = GetParameter("two", parameters, guards.number);
console.log(undefinedParam); // undefined
Note how guards.number
takes the place of <number>
. And if you inspect the type of numberParam
it is number | undefined
, and the returned values are what you expect.
Okay, hope that helps; good luck!