I have this function:
interface NumDict<T> {
[key : number] : T
}
export function mapNumDictValues<T,R>(dict: NumDict<T>, f: (v: T, key?: number) => R): NumDict<R> {
let emptyDict : NumDict<R> = {};
return Object.keys(dict).reduce((acc, key) => {
const keyInt = parseInt(key);
acc[keyInt] = f(dict[keyInt], keyInt);
return acc;
}, emptyDict);
}
Now I would like it to work for string indexed dictionaries as well as number indexed dictionaries, e.g. something like:
function mapDictValues<K extends string|number,T,R>(obj: {[id: K]: T}, f: (v: T, key?: K) => R): {[id: K]: R} {
However, this gets me this error:
error TS1023: An index signature parameter type must be 'string' or 'number'.
Is there a way?
Try this:
interface IStringToNumberDictionary {
[index: string]: number;
}
interface INumberToStringDictionary {
[index: number]: string;
}
type IDictionary = IStringToNumberDictionary | INumberToStringDictionary;
Example:
let dict: IDictionary = Object.assign({ 0: 'first' }, { 'first': 0 });
let numberValue = dict["first"]; // 0
let stringValue = dict[0]; // first
In your case something like this:
interface IStringKeyDictionary<T> {
[index: string]: T;
}
interface INumberKeyDictionary<T> {
[index: number]: T;
}
type IDictionary<T> = IStringKeyDictionary<T> | INumberKeyDictionary<T>;
What you are after is not easily accomplished (at least I can't find a way) because of how javascript treats the object keys (strings only) and typescript restrictions on index expressions (string/number/symbol/any) and the difference between the types number | string
and number
/string
.
The only type-safety you managed to gain (from what I understand) is that you get an error using noImplicitAny
(obscure error) for one case, but you are still restricted with what you can do with this dictionary.
I don't know much about your case, but it sounds like you have little to gain here in order to get decent solution, unless you really need this type-safety then I think it's better to just treat your keys as strings and get it over with, but if you do need it then I suggest that you create your own dictionary type implementations to deal with things, for example:
interface Dict<K extends number | string, V> {
get(key: K): V;
set(key: K, value: V): void;
mapValues<R>(f: (v: V, key?: K) => R): Dict<K, R>;
}
abstract class BaseDict<K extends number | string, V> implements Dict<K, V> {
protected items: { [key: string]: V } = Object.create(null);
get(key: K): V {
return this.items[key.toString()];
}
set(key: K, value: V): void {
this.items[key.toString()] = value;
}
abstract keys(): K[];
values(): V[] {
return Object.keys(this.items).map(key => this.items[key]);
// or Object.values(this.items) with ES6
}
mapValues<R>(fn: (v: V, key?: K) => R): Dict<K, R> {
let dict: Dict<K, R> = Object.create(this.constructor.prototype);
this.keys().forEach(key => dict.set(key, fn(this.get(key), key)));
return dict;
}
}
class NumDict<V> extends BaseDict<number, V> {
keys(): number[] {
return Object.keys(this.items).map(key => parseFloat(key));
}
}
class StringDict<V> extends BaseDict<string, V> {
keys(): string[] {
return Object.keys(this.items);
}
}
You'll need to create your dicts with the ctors, which is not as comfortable as using {}
, but you do have more control over things, for example notice that when calling the map function (fn
) in BaseDict.mapValues
then the key will have the right type (number in case of NumDict
and string for StringDict
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With