Skip to main content
Fixed code sample with Mapped Types
Source Link

Edit: I've updated the code and uploaded a gist that will work in the TS playground - the gist will compile with Typescript 3.0's strictest settings (at least all the strict settings available to the playground)1.

export interface Dictionary<K, V> {
  getKeys(): K[];
  getValues(): V[];
  get(key: K): V;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: Partial<Record<K,{ V>>;[key in K]?: V };

  constructor() {
    this.internalDict = {};
  }

  public getKeys(): K[] {
    let keys: K[] = [];
    for(let key in this.internalDict) {
      keys.push(key);
    }

    return keys;
  }

  public// getValuesType predicate to ensure v exists
  private exists(v: V | undefined): V[]v is V {
    return v != null && typeof v !== "undefined";
  }

  public getValues() {
    let vals: V[] = [];

    for(let key in this.internalDict) {
      valslet v = this.pushinternalDict[key];
      if(this.internalDict[key]exists(v)) {
        vals.push(v);
      }
    }

    return vals;
  }

  public get(key: K): V {
    returnlet 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 type

Edit: I've updated the code and uploaded a gist that will work in the TS playground - the gist will compile with Typescript 3.0's strictest settings (at least all the strict settings available to the playground).

export interface Dictionary<K, V> {
  getKeys(): K[];
  getValues(): V[];
  get(key: K): V;
  put(key: K, val: V): void; // or boolean?
}


export class JSDictionary<K extends string, V> implements Dictionary<K, V> {

  private internalDict: Partial<Record<K, V>>;

  constructor() {
    this.internalDict = {};
  }

  public getKeys(): K[] {
    let keys: K[] = [];
    for(let key in this.internalDict) {
      keys.push(key);
    }

    return keys;
  }

  public getValues(): V[] {
    let vals: V[] = [];

    for(let key in this.internalDict) {
      vals.push(this.internalDict[key]);
    }

    return vals;
  }

  public get(key: K): V {
    return this.internalDict[key];
  }

  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 type
let myFox = myAnimalPen.get('FOX'); // myFox is an Animal type

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.

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 type
Added a link to an up to date gist that will compile with Typescript 3.0 and work with strict mode.
Source Link

Edit: I've updated the code and uploaded a gist that will work in the TS playground - the gist will compile with Typescript 3.0's strictest settings (at least all the strict settings available to the playground).

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.

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.

Edit: I've updated the code and uploaded a gist that will work in the TS playground - the gist will compile with Typescript 3.0's strictest settings (at least all the strict settings available to the playground).

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.

Source Link

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<K, V> {
  getKeys(): K[];
  getValues(): V[];
  get(key: K): V;
  put(key: K, val: V): void; // or boolean?
}


export class JSDictionary<K extends string, V> implements Dictionary<K, V> {

  private internalDict: Partial<Record<K, V>>;

  constructor() {
    this.internalDict = {};
  }

  public getKeys(): K[] {
    let keys: K[] = [];
    for(let key in this.internalDict) {
      keys.push(key);
    }

    return keys;
  }

  public getValues(): V[] {
    let vals: V[] = [];

    for(let key in this.internalDict) {
      vals.push(this.internalDict[key]);
    }

    return vals;
  }

  public get(key: K): V {
    return this.internalDict[key];
  }

  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<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 type
let myFox = myAnimalPen.get('FOX'); // myFox is an Animal type

Major 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.