Type-safe Dictionary in TypeScript
Tags: typescript, react
Published: 2021-12-20 12:30:37

Although `Map` object has almost everything I need, I recently found myself in need of `map()` function on a `Map `object... No such luck. ;)
So I implemented it.

```
export interface IDictionary<T> {
    set(key: string, value: T);
    get(key: string): T;
    delete(key: string): boolean;
    hasKey(key: string): boolean;
    hasValue(value: T): boolean;
    getKeys(): string[];
    getValues(): T[];
    map(fn: (key: string, value: T) => any ): {};
    
    size: number;
}
export class Dictionary<T> implements IDictionary<T>{ 
    private items: { [key: string]: T } = {};
    
    public set(key: string, value: T) {
        this.items[key] = value;
    }
    public get(key: string): T {
        return this.items[key];
    }
    public delete(key: string):boolean { 
        if (this.items.hasOwnProperty(key)) { 
            delete this.items[key];
            return true;
        }
        else { 
            return false;
        }
    }

    public hasKey(key: string): boolean {
        return this.items.hasOwnProperty(key);
    }
    public hasValue(value:T): boolean { 
        return Object.values(this.items).includes(value);
    }

    public getKeys(): string[] {
        return Object.keys(this.items);
    }
    public getValues(): T[] {
        return Object.values(this.items);
    }

    public map(mapFn: { (key: string, value:T): any; }): {} { 
        return Object.keys(this.items).map((key) => { 
            return mapFn(key,this.items[key]);
        });
       
    }
    public format(mapFn: { (key: string, value:T): any; }): any[] { 
        return Object.keys(this.items).map((key) => { 
            return mapFn(key,this.items[key]);
        });
    }

    public get size(): number { 
        return Object.keys(this.items).length;
    }
}
```

Now, printing Dictionary in a React function couldn't be easier

```
export default function CommitForm(props: ICommitFormProps) {
    let myDictionary: Dictionary<Array<string>> = (new Dictionary());
...
    return (
        <ul>
            {
                myDictionary.map((key: string, value: string[]) => {
                    return (<li>{value.join(", ")} (type {key}) </li>);
                })
            }
        </ul>
    );
...
}
```

And in case you want to use the Dictionary in your localized string:
##### myStrings.d.ts
```
  Warning_HTML: string;
```
##### en-us.js
```
  "Warning_HTML":"The following values are not supported:<ul>{0}</ul>"
```    

##### MyReactComponent.tsx
```
export default function CommitForm(props: ICommitFormProps) {
    let myDictionary: Dictionary<Array<string>> = (new Dictionary());
...

    function getHTML(fields: Dictionary<string[]>): string { 
        return (fields.format((key, value) => { return "<li>" + value.join(", ") + " (type " + key + ") </li>"; })).join();
    }

    return (
    <MessageBar messageBarType={MessageBarType.warning} isMultiline={true} >
    {
        <div dangerouslySetInnerHTML={{ __html: format(strings.Warning_HTML, getHTML(myDictionary)) }} ></div>
    }
    </MessageBar>
    );
...
}
```




The difference between `Object`, `Map `and `Record ` is widely documented (see e.g. [Building a type-safe dictionary in TypeScript](https://blog.logrocket.com/building-type-safe-dictionary-typescript/) for an explanation).