Home > front end >  Generic Types: Dynamically type check specific object properties: using key ~ value pair from Type A
Generic Types: Dynamically type check specific object properties: using key ~ value pair from Type A

Time:04-09

I currently have a type (FieldTypeProperties) that I want to enforce dynamic type checking when it's keys are passed to a function or object:

export enum supportedFields{
    Text = "Text",
    Note = "Note",
    Number = "Number"
}

export type FieldTypeProperties = {
    [supportedFields.Text]: TypeA;
    [supportedFields.Note]: TypeB;
    [supportedFields.Number]: TypeC;
};

I've managed to define a function that behaves as expected:

const createField = <K extends keyof FieldTypeProperties>(fieldType: K, properties: FieldTypeProperties[K]): IResult => {

  let field: IResult;

  //Some code

  return field;

}

The usage works as expected and the fieldType argument value does check for the correct Type in the properties argument:

let test = createField(supportedFields.Note, {});
// Checks for Type B in the properties argument, as defined in the FieldTypeProperties type

However for batch operations, I want to loop an array of objects that conform to the arguments in createField(), so:

type BatchObj<K extends keyof FieldTypeProperties> = {
    fieldType: K;
    properties: FieldTypeProperties[K];
}

// Intended Usage:
let testBatch1: BatchObj<keyof FieldTypeProperties>[] = [
  {
    fieldType: supportedFields.Text,
    properties: {}, //Type A should only be checked, but it's not, instead a union of TypeA | TypeB | TypeC is checked
  },
]

// Usage that works but is NOT desired:
let testBatch2: BatchObj<supportedFields.Text>[] = [
  {
    fieldType: supportedFields.Text,
    properties: {}, //Type A is checked, but now any additional objects added to this array must have a fieldType = supportedFields.Text and properties = TypeA
  }
]

I can understand why this is happening, since in testBatch1: fieldType (K) would the set of keys for the FieldTypeProperties type, and properties (FieldTypeProperties[K]) would be the set of values for the FieldTypeProperties type.

Is there a way to achieve my desired usage, based on what is inferred in the code snippet above? Or is this a typescript limitation and as it stands is not a possibility?

Your responses will be greatly appreciated :)

CodePudding user response:

You can generate a union for each possible key : BatchObj<"Text"> | BatchObj<"Note"> | BatchObj<"Number"> using the distribution of conditional types. This will ensure the relationship between fieldType and properties is preserved:

type BatchObj<K extends keyof FieldTypeProperties> = {
    fieldType: K;
    properties: FieldTypeProperties[K];
}
type BatchObjUnion<K extends keyof FieldTypeProperties = keyof FieldTypeProperties>  = K extends K ? BatchObj<K>: never

// Intended Usage:
let testBatch1: BatchObjUnion[] = [
  {
    fieldType: supportedFields.Text,
    properties: { text: "" }, //Type A
  },
]

Playground Link

  • Related