2

I know there are some posts related, however, I don't get to accomplish that:

I want to type guard and object which has a generic key to verify that is a string for a dictionary. Is it possible?

interface IDictionary<T> {
  [key: string]: T | undefined;
}

class HashArray<T, E extends keyof T & string> {
  private key: E;
  private dict: IDictionary<T>;
  private arr: T[];

  constructor(theyKey: E) {
    this.key = theyKey;
    this.dict = {};
    this.arr = [];
  }

  public push(elem: T) {
    this.arr.push(elem);

    if (this.isValidKey(elem[this.key])) this.dict[elem[this.key]] = elem; // Error
  }

  private isValidKey(key: T[E]): key is string { // Error
    return typeof key === "string";
  }
}

class Sample {
  oneKey: string = "1";
  anotherKey: number = 0;
}

const oneKey = new HashArray<Sample, 'oneKey'>('oneKey');
const secondKey = new HashArray<Sample, 'anotherKey'>('anotherKey');

oneKey.push(new Sample()); // Works
secondKey.push(new Sample()); // It should fail because anotherKey is number

Sandbox

The compilation shows two errors:

Type 'T[E]' cannot be used to index type 'IDictionary'

A type predicate's type must be assignable to its parameter's type. Type 'string' is not assignable to type 'T[E]'

How can I avoid that error without disable it?

SnakeDrak
  • 3,406
  • 4
  • 28
  • 41
  • 1
    It's unclear to me exactly what `isValidKey` is supposed to do. If you want to check for the exitance of the key on the object, you need to pass in the object ... right now the type guards looks like a simple string guard.. not much to do with T or E .. – Titian Cernicova-Dragomir Jun 25 '20 at 10:30
  • Maybe the first sample was confusing. I have updated the answer to be more precise with my example @TitianCernicova-Dragomir – SnakeDrak Jun 25 '20 at 10:51

1 Answers1

2

Since you want to narrow the type of elem that is what you need to pass to the type guard. Also if you want T[E] to be of type string keyof T & string is not the way to do it, you will need a more complex mapped conditional type, you can read here about it

interface IDictionary<T> {
  [key: string]: T | undefined;
}
type KeyOfType<T, V> = {
    [P in keyof T]-?: T[P] extends V ? P : never
}[keyof T];
class HashArray<T, E extends KeyOfType<T, string>> {
  private key: E;
  private dict: IDictionary<T>;
  private arr: T[];

  constructor(theyKey: E) {
    this.key = theyKey;
    this.dict = {};
    this.arr = [];
  }

  public push(elem: T) {
    this.arr.push(elem);

    if (this.isValidKey(elem)) this.dict[elem[this.key]] = elem;
  }

  private isValidKey(elem: T): elem is T & Record<E, string> {
    return typeof elem[this.key] === "string";
  }
}

class Sample {
  oneKey: string = "1";
  anotherKey: number = 0;
}

const oneKey = new HashArray<Sample, "oneKey">("oneKey");
const secondKey = new HashArray<Sample, "anotherKey">("anotherKey");  // Fails here, Sample["anotherKey"] is not a string

oneKey.push(new Sample()); // Works
secondKey.push(new Sample()); 

Playground Link

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357