patterntypescriptMinor
TypeScript Dictionary class
Viewed 0 times
dictionarytypescriptclass
Problem
I recently implemented a dictionary class in TypeScript. I'm still new to JavaScript so I'm curious what people think about it.
I realized that objects in JavaScript act a lot like dictionaries so I'm basically just adding functionality to the object. I'm not sure how I would want to handle sorting. I think I would prefer to create a method that returned a sorted array or
class Dictionary {
public constructor ();
public constructor (object: Object);
public constructor (object?) {
if (object != null) {
for (let property in object) {
if (object.hasOwnProperty(property)) {
this[property] = object[property];
}
}
}
}
public clone(): Dictionary {
let result = new Dictionary(this);
return result;
}
public getKeys(): string[] {
let result = [];
for (let item in this) {
if (this.hasOwnProperty(item)) {
result.push(item);
}
}
return result;
}
public getValues(): any[] {
let result = [];
for (let item in this) {
if (this.hasOwnProperty(item)) {
result.push(this[item]);
}
}
return result;
}
public tryAddItem(key: string, value: any): boolean {
let isAddItem = !this.hasOwnProperty(key) && typeof(value) !== 'undefined';
if (isAddItem) {
this[key] = value;
}
return isAddItem;
}
public tryUpdateItem(key: string, value: any): boolean {
let isUpdateItem = this.hasOwnProperty(key) && typeof(value) !== 'undefined';
if (isUpdateItem) {
this[key] = value;
}
return isUpdateItem;
}
public tryDeleteItem(key: string): boolean {
let isDeleteItem = this.hasOwnProperty(key);
if (isDeleteItem) {
delete this[key];
}
return isDeleteItem;
}
}I realized that objects in JavaScript act a lot like dictionaries so I'm basically just adding functionality to the object. I'm not sure how I would want to handle sorting. I think I would prefer to create a method that returned a sorted array or
Solution
Edit: I've updated the code and uploaded a gist that will work in the TS playground - the gist will compile with Typescript 3.1.
The primary benefit of Typescript is to provide types for Javascript. And since Typescript provides Generics - I don't think your implementation of a Dictionary provides any benefit over a plain javascript object.
In fact, as you noted in the comments, JS Objects (ie TS object) are dictionaries. You just need to provide types around them for your dictionary. (btw - I also come from a C# background, but I've also come to love the underlying functional nature of JS that TS lets us type).
Here's a code sample of the direction I would expect a
Example Usage
Major points:
The primary benefit of Typescript is to provide types for Javascript. And since Typescript provides Generics - I don't think your implementation of a Dictionary provides any benefit over a plain javascript object.
In fact, as you noted in the comments, JS Objects (ie TS object) are dictionaries. You just need to provide types around them for your dictionary. (btw - I also come from a C# background, but I've also come to love the underlying functional nature of JS that TS lets us type).
Here's a code sample of the direction I would expect a
Dictionary.ts class to have in a code base I work in. Now that we have Mapped Types, we can generalize dictionaries better.export interface Dictionary {
getKeys(): K[];
getValues(): V[];
get(key: K): V | null; // the key might not exist
put(key: K, val: V): void; // or boolean?
}
export class JSDictionary implements Dictionary {
private internalDict: { [key in K]?: V };
constructor() {
this.internalDict = {};
}
public getKeys() {
let keys: K[] = [];
for(let key in this.internalDict) {
keys.push(key);
}
return keys;
}
// Type predicate to ensure v exists
private exists(v: V | undefined): v is V {
return v != null && typeof v !== "undefined";
}
public getValues() {
let vals: V[] = [];
for(let key in this.internalDict) {
let v = this.internalDict[key];
if(this.exists(v)) {
vals.push(v);
}
}
return vals;
}
public get(key: K) {
let v = this.internalDict[key];
return this.exists(v)
? v
: null;
}
public put(key: K, val: V): void {
this.internalDict[key] = val;
}
}Example Usage
type myKeys = 'FOX' | 'CAT' | 'DOG';
interface Animal {
species: string;
name: string;
weight: number;
}
// A dictionary that hols one fox/cat/dog.
let myAnimalPen = new JSDictionary();
myAnimalPen.put('FOX', { name: 'Foxworth', species: 'Fox', weight: 40 });
// a dictionary that takes any string and maps it to a number
let idDict = new JSDictionary();
idDict.put('somehas', 1204);
idDict.put('yeahaasd', 3306);
let yeaID = idDict.get('yeahaasd'); // yeaID is a number | null type
let myFox = myAnimalPen.get('FOX'); // myFox is an Animal | null typeMajor points:
- Code to an interface as much as you can in typescript. Affords you flexibility
- Generic types
- Transparent use of Plain JS object as the underlying dictionary (JS Engines optimize this very use case!)
- I removed overloaded constructors, but you could add that back in with the proper types and it will work as expected
- Left out clone - pretty easy to implement though
- No exception handling when get returns null, like your 'try*' functions. I actually like try functions like you have, so you could mostly copy/paste, although with the stricter typings sometimes that will be taken care of by the compiler for you.
Code Snippets
export interface Dictionary<K, V> {
getKeys(): K[];
getValues(): V[];
get(key: K): V | null; // the key might not exist
put(key: K, val: V): void; // or boolean?
}
export class JSDictionary<K extends string, V> implements Dictionary<K, V> {
private internalDict: { [key in K]?: V };
constructor() {
this.internalDict = {};
}
public getKeys() {
let keys: K[] = [];
for(let key in this.internalDict) {
keys.push(key);
}
return keys;
}
// Type predicate to ensure v exists
private exists(v: V | undefined): v is V {
return v != null && typeof v !== "undefined";
}
public getValues() {
let vals: V[] = [];
for(let key in this.internalDict) {
let v = this.internalDict[key];
if(this.exists(v)) {
vals.push(v);
}
}
return vals;
}
public get(key: K) {
let v = this.internalDict[key];
return this.exists(v)
? v
: null;
}
public put(key: K, val: V): void {
this.internalDict[key] = val;
}
}type myKeys = 'FOX' | 'CAT' | 'DOG';
interface Animal {
species: string;
name: string;
weight: number;
}
// A dictionary that hols one fox/cat/dog.
let myAnimalPen = new JSDictionary<myKeys, Animal>();
myAnimalPen.put('FOX', { name: 'Foxworth', species: 'Fox', weight: 40 });
// a dictionary that takes any string and maps it to a number
let idDict = new JSDictionary<string, number>();
idDict.put('somehas', 1204);
idDict.put('yeahaasd', 3306);
let yeaID = idDict.get('yeahaasd'); // yeaID is a number | null type
let myFox = myAnimalPen.get('FOX'); // myFox is an Animal | null typeContext
StackExchange Code Review Q#134030, answer score: 7
Revisions (0)
No revisions yet.