Reputation: 15356
Let's say we are writing DB model for Post
, and because database stores everything as string we need to write parse
function that would take raw DB object and cast it to proper Post
interface.
To reproduce set noImplicitReturns: true
interface Post {
id: number
text: string
}
function parse<K extends keyof Post>(k: K, v: any): Post[K] {
switch(k) {
case 'id': return parseInt(v)
case 'text': return v.toString()
}
}
There are two errors in that code. First - it won't compile, it seems like TypeScript didn't realises that our code is correct and ask for default
statement to be added to the switch
.
The second error - it won't detect that you are checking against wrong value. The wrong code below would compile without errors
function parse<K extends keyof Post>(k: K, v: any): Post[K] {
switch(k) {
case 'id': return parseInt(v)
case 'text': return v.toString()
case 'some': return v.toString() // <= error, no `some` key
default: return '' // <= this line is not needed
}
}
There's even third error, TypeScript would allow to return wrong value for the key, this wrong code would compile
function parse<K extends keyof Post>(k: K, v: any): Post[K] {
switch(k) {
case 'id': return parseInt(v)
case 'text': return 2 // <= error, it should be string, not number
default: return ''
}
}
Are those TypeScript limitations or I'm dong something wrong?
Upvotes: 2
Views: 275
Reputation: 2154
Would this alternative appeals to you ?
interface Post {
id: number
text: string
}
type Parsers = {
[k in keyof Post]: (v: any) => Post[k]
}
const parsers: Parsers = {
id (v: any) {
return parseInt(v)
},
text (v: any) {
return v.toString(v)
}
}
parsers.id('123') // number
parsers.text({}) // string
// In addition, adding extra parsers or returning a value of wrong type from a parser is type-checked.
Update
Making Parser(renamed from Parsers)
type declaration generic and exploiting arrow function and contextual type inference, the code can be improved to:
type Parser<T> = {
[k in keyof T]: (v: any) => T[k]
}
interface Post {
id: number
text: string
}
const parser: Parser<Post> = {
id: v => parseInt(v),
text: v => v.toString()
}
Upvotes: 1
Reputation: 3387
Firstly, K
extends keyof Post
doesn't mean K
is keyof Post
, so instead of using generic type K
, you must use keyof Post
to limit the potential values of K
.
Secondly, the default option is forced if you set "switch-default": true,
in your tsconfig.json. So set it to false
then the issue will go away.
Thirdly, Post[K]
or even Post[keyof Post]
will return all the possible types of Post
properties, so number
is possible. Typescript doesn't force Post[K]
to the type of its K
property, unless you point out which K
is. Otherwise you must define the mapping type, like type A = ['id', numer] | [text', string]
and define Post
as [A[0]]: A[1]
Hope this help
Upvotes: 2