James Hay
James Hay

Reputation: 7315

Can I get a generic type from an implementation of a property?

I have the following object (for testing only):

{
  type: 'custom',
  source: (): number[] => { return [1,2,3,4] },
  filters: [
    {
      text: 'Is in one of',
      value: 'iiof',
      action: (v: MainType, k: number[]) => true
    },
    {
      text: 'Is not in one of',
      value: '!iiof',
      action: (v: MainType, k: number[]) => true
    }
  ]

}

And I'm trying to build the types, so that the second parameter of any filter.action function, will be the return type of source. I can use any, but would prefer to have this typed if possible.

My current types are the following:

export type FilterCustomImpl<T, K> = {
  text: string;
  value: string;
  action: (v: T, s: K[]) => boolean;
};

export type FilterCustomSchemaField<T, K = any> = {
  type: 'custom',
  display?: string;
  source: () => K[];
  filters: FilterCustomImpl<T, K>[]
};

export type FilterSchemaField<T> = 
    FilterEnumSchemaField | 
    FilterNumericSchemaField | 
    FilterStringSchemaField | 
    FilterCustomSchemaField<T>;

export type FilterSchemaFields<T> = { [index: string]: FilterSchemaField<T> };

export type FilterSchema<T> = {
  defaultKey: string;
  fields: FilterSchemaFields<T>
};

So the parent FilterSchema could have an infinite number of FilterCustomSchemaFields with different K types.

Currently when building the filter schema I can get around it by specifying the type on the field itself, so

{
  type: 'custom',
  source: (): number[] => { return [1,2,3,4] },
  filters: [
    {
      text: 'Is in one of',
      value: 'iiof',
      action: (v: MainType, k: number[]) => true
    },
    {
      text: 'Is not in one of',
      value: '!iiof',
      action: (v: MainType, k: number[]) => true
    }
  ]
} as FilterCustomSchemaField<MainType, number>

But it's not a great approach because it obviously requires extra typing, and has to rewrite the main T type each time.

It would be ideal if I could infer the K type from the return type of the source function, but I'm not sure that's possible.

Upvotes: 0

Views: 47

Answers (1)

jcalz
jcalz

Reputation: 327634

I'd probably use a curried helper function (the currying is to work around the lack of partial type parameter inference, more details in this answer) so that you can manually specify T just once, and have the compiler infer the value of U. (I know you called it K, but the convention is that K and P are used for key types, whereas T and U are used for more general types... and since you're not constraining it to key types to something like string | number | symbol I will change the name.)

Here it is:

const asFilterCustomSchemaField = <T>() => <U>(
  x: FilterCustomSchemaField<T, U>
) => x;

And here's how it's used:

const filterCustomSchemaField = asFilterCustomSchemaField<MainType>()({
  type: "custom",
  source: () => [1, 2, 3, 4], 
  filters: [
    {
      text: "Is in one of",
      value: "iiof",
      action: (v, k) => true // v is inferred as MainType, k as number[]
    },
    {
      text: "Is not in one of",
      value: "!iiof",
      action: (v, k) => true // v is inferred as MainType, k as number[]
    }
  ]
});
// const filterCustomSchemaField: FilterCustomSchemaField<MainType, number>

Note how the value is of type FilterCustomSchemaFiled<MainType, number> as desired, where number is just inferred from the value, and MainType is specified. If you use IntelliSense and hover over v and k in the action properties, you will see they are inferred as MainType and number[] respectively, also as desired.

Okay, hope that helps; good luck!

Link to code

Upvotes: 1

Related Questions