简体   繁体   中英

TypeScript generic key/property type guard

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?

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM