Search code examples
typescripttypescript-typingstypescript-genericstype-safety

typescript typesafety for generic graph-like data-structure class


I was trying to implement a graph-like data structure in typescript and was type safing it, with difficulty, after many solutions I arrived at this:

interface Cities {
  lisbon: any
  porto: any
  faro: any
}

class Graph<T> {
  adjacencyList: {
    [K in keyof T]?: (keyof T)[]
  } = {}

  addVertex(value: keyof T) {
    if (!this.adjacencyList[value]) this.adjacencyList[value] = []
  }

  addEdge(vertex1: keyof T, vertex2: keyof T) {
    this.adjacencyList[vertex1]?.push(vertex2)
    this.adjacencyList[vertex2]?.push(vertex1)
  }
}

is there a more elegant way of doing it? I wanted to use generics in order to be more versatile. Is something like this possible?

enum Cities {
  "lisbon",
  "porto",
  "faro"
}

class Graph<T> {
  adjacencyList: {
    [K in T]?: T[]
  } = {}

  addVertex(value: T) {
    if (!this.adjacencyList[value]) this.adjacencyList[value] = []
  }

  addEdge(vertex1: T, vertex2: T) {
    this.adjacencyList[vertex1]?.push(vertex2)
    this.adjacencyList[vertex2]?.push(vertex1)
  }
}

says T is not assignable to type symbol...


Solution

  • That's because typescript has no guarantee that all the values in T will actually be strings, numbers, or symbols (the only valid object keys as far as typescript is concerned). Think about what would happen if you fed number[] as T. So, add a clause that it has to extend string | number | symbol:

    class Graph<T extends string | number | symbol> {
      adjacencyList: {
        [K in T]?: T[]
      } = {}
    
      addVertex(value: T) {
        if (!this.adjacencyList[value]) this.adjacencyList[value] = []
      }
    
      addEdge(vertex1: T, vertex2: T) {
        this.adjacencyList[vertex1]?.push(vertex2)
        this.adjacencyList[vertex2]?.push(vertex1)
      }
    }
    

    Then, to feed an enum to it, use keyof typeof Enum to get the union type consisting of all possible strings:

    const foo = new Graph<keyof typeof Cities>();