Craig
Craig

Reputation: 53

Can I use Typescript generics to filter keys from an object based on type?

I have a filter function working, but I can't for the life of me find out how to write a generic type that communicates this returned data structure:

class Value<T> {
  val: T;

  constructor(val: T){
    this.val = val;
  }

  get(): T {
    return this.val;
  }
}

let data = {
  foo: new Value(123),
  bar: new Value('abc'),
  baz: 'xyz'
}

let filter = (obj: Object): any => {
  let result: any = {};
  for (const [key, val] of Object.entries(obj)) {
    if(val instanceof Value) result[key] = val.get();
  }
  return result;
};

filter(data).foo; // 123
filter(data).bar; // 'abc'
filter(data).baz; // error

I've managed to do some somewhat complex things with generics, but I think this is way beyond my depth. Any help would be greatly appreciated.

Upvotes: 0

Views: 789

Answers (1)

jsejcksn
jsejcksn

Reputation: 33691

I think this is what you're looking for:

TS Playground link

type Filtered<T> = {
  [K in keyof T as T[K] extends Value<any> ? K : never]: T[K] extends Value<any>
    ? ReturnType<T[K]['get']>
    : never;
};

function filter<T extends Record<string, unknown>>(obj: T): Filtered<T> {
  const result = {} as Filtered<T>;
  for (const [key, val] of Object.entries(obj)) {
    if (val instanceof Value) result[key as keyof typeof result] = val.get();
  }
  return result;
}

Upvotes: 2

Related Questions