'Type 'K' cannot be used to index type '{ [key in keyof K]: V; }'.ts(2536)
I want to make a method that returns a new object from my custom Object type.
/*
type K represent the type of key in an object
type V represent the type of value in an object
*/
class Group<K,V> extends Map<K,V> {
toObject() {
const result: { [key in keyof K]: V } = {} as { [key in keyof K]: V }
for (const [key, value] of this.entries()) {
result[key] = value //Error:Type 'K' cannot be used to index type '{ [key in keyof K]: V; }'.ts(2536)
}
return result
}
}
key and value have types K and V respectively.
I don't clearly understand the error message it would be good if you explain it to me.
I also tried to make it easier.
class Group<K, V> extends Map<K, V> {
toObject() {
const result = {}
for (const [key, value] of this.entries()) {
result[key] = value //Error:Type 'K' cannot be used to index type '{}'.ts(2536)
}
return result
}
}
Solution 1:[1]
The main problem here is that you were using keyof K instead of just K. The keyof type operator gives you the union of the keys (often literal types like "a") of the type it applies to. keyof {a: 1, b: 2, c: 3} is "a" | "b" | "c". So keyof K would be whatever keys you'd expect a value of type K to have. If K is something like string, then keyof K is something like keyof string which is:
type KeyofString = keyof string;
/* type KeyofString = number | typeof Symbol.iterator | "toString" | "charAt" |
"charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" |
"match" | "replace" | "search" | "slice" | "split" | "substring" |
"toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" |
"trim" | "length" | "substr" | "valueOf" | "codePointAt" | "includes" |
"endsWith" | "normalize" | "repeat" | "startsWith" | "anchor" | "big" |
"blink" | "bold" | "fixed" | "fontcolor" | "fontsize" | "italics" |
"link" | "small" | "strike" | "sub" | "sup" | "padStart" | "padEnd" */
Unless you want result to have keys like "toUpperCase" or "trim", then keyof K is not right.
If result[key] makes sense, and if key is of type K, then you want a type you can index into with keys of type K. If you put a value value of type V into that property, then you want a type whose values are of type V. The mapped type {[P in K]: V} is just such a type; the keys are K and the values are V. This sort of mapped type where the value types don't depend on the specific keys is often called a "record" and is used often enough that there's a Record<K, V> utility type provided for this purpose.
Let's use Record<K, V> then:
class Group<K, V> extends Map<K, V> {
toObject() {
const result = {} as Record<K, V>; // error!
// -----------------------> ~
// Type 'K' does not satisfy the constraint 'string | number | symbol'
for (const [key, value] of this.entries()) {
result[key] = value // okay
}
}
}
So, that resolves the result[key] = value error, but it introduces a new error. The type Record<K, V> is an error in K, where K does not satisfy a constraint of being a keylike type, string | number | symbol. Note that string | number | symbol is used often enough that there is also a PropertyKey utility type provided to be a short alias for this.
And that really is a problem if your Group<K, V> can really have an arbitrary type for K. A Map object in JavaScript can hold "keys" of any type whatsoever, while a plain object like result can only hold keys of a PropertyKey type. For example, you can use Date objects as "keys" to a Map but not as keys to a plain JavaScript object:
const date = new Date();
const m = new Map<Date, string>();
m.set(date, "hello"); // okay
const o: any = {};
o[date] = "hello"; // error
So if your Group<K, V> really needs a value of type Record<K, V>, then that means you have to constrain K so that the compiler will only let new Group<K, V> exist when K is sufficiently keylike:
class Group<K extends PropertyKey, V> extends Map<K, V> {
toObject() {
const result = {} as Record<K, V>; // okay
for (const [key, value] of this.entries()) {
result[key] = value // okay
}
}
}
Now everything compiles as desired.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | jcalz |
