Type definitions for some objects are a little wonky. I know the properties and I know the type but the compiler doesn't. I want to write a small function that extracts the property with the type that I expect, but throws an error if the type is wrong.
So I want to know if the "readProperty" function below can somehow tell the compiler that e.g. the extracted property is a number, because the developer wrote "number" when invoking the function
Is this possible?
function readProperty<T>(obj: T, key: keyof T, typeName: "string" | "number"): string | number {
const value = obj[key]
if (typeof value != typeName) {
throw new Error(`Property ${key.toString()} must be a ${typeName}`)
}
return value
}
const someObj = {
x: "123",
y: 123,
}
const x = readProperty(someObj, "x", "number") // <-- compiler should "know" now that x is a number
const y = readProperty(someObj, "y", "string") // <-- compiler should "know that y is a string
For this use case I don't think there's much use for making the function generic in the type of
objor the type ofkey. If the compiler actually knew enough aboutobjandkeyfor such a generic call signature to be useful, you wouldn't need to do any further checking of the property type (or worse, the compiler would disagree with you about the type).Instead, the important part is to get the call signature so that when you pass a value of string literal type
"string"astypeNamethen the output of the function is of typestring, and if you pass"number"then the output is of typenumber. The most straightforward way to represent a mapping between a string literal input type and an arbitrary output type is to use an object type like aninterface, and then looking up the output property involves an indexed access type on the input string literal. Like this:So
readProperty()is generic inK, the type oftypeNamewhich is constrained to be one of the keys of theTypeofMapinterface... so either"string"or"number". And then the return type of the function isTypeofMap[K], the corresponding typestringornumber.Note that the compiler cannot really verify that the implementation of
readPropertyconforms to the call signature. So I have asserted thatobjis of theanytype via(obj as any)to loosen the type checking inside the function body enough to prevent errors. That means you need to be careful that the implementation does the right thing. If you were to change, say,(typeof value != typeName)into(typeof value == typeName), the compiler would not notice or complain. So take care.Anyway, let's see if it works from the caller's side:
Looks good!
Playground link to code