Home > OS >  Typescript type string is not assignable to type keyof when I get value from object
Typescript type string is not assignable to type keyof when I get value from object

Time:02-21

Could someone explains why in the example code below typescript gives the error message "const key: string Argument of type 'string' is not assignable to parameter of type 'keyof Condition"

interface Condition {
    GOOD: 'GOOD';
    BAD: 'BAD';
    NONE: 'NONE';
}

const ObjWithAllCondition: Condition = {
    GOOD: 'GOOD',
    BAD: 'BAD',
    NONE: 'NONE',
};

interface Result {
    condition: keyof Condition;
    count: number;
}

const getKeyValue = <T extends Condition, K extends keyof T>(obj: T, key: K) => obj[key];

const getResult = (): Result[] => {
    const result = [];
    for (const key in ObjWithAllCondition) {
        result.push({
            condition: getKeyValue(ObjWithAllCondition, key), // this is error
            count: 1,
        });
    }

    return result;
};

CodePudding user response:

Because TypeScript uses a structural type system, all objects can have extra properties beyond what exists in its defined type (and the compiler is ok with this as long as the properties that are known are assignable to the type of the object).

Because of this, using the in operator or methods which iterate an object's keys might return values which are not in the type that you're expecting, so all of those keys/properties are of the type string (for type safety).

You can refactor your example to derive the properties in your Condition type from an array of the possible keys, and then use that array when iterating the keys. Here's an example:

TS Playground

const conditions = ['GOOD', 'BAD', 'NONE'] as const;
type ConditionName = typeof conditions[number];

type StringUnionToKV<S extends string> = { [K in S]: K };

type Condition = StringUnionToKV<ConditionName>;

const obj: Condition = {
  GOOD: 'GOOD',
  BAD: 'BAD',
  NONE: 'NONE',
};

type Result = {
  condition: ConditionName;
  count: number;
};

const getKeyValue = <T extends Condition, K extends keyof T>(obj: T, key: K) => obj[key];

const getResult = (): Result[] => {
  const result = [];

  for (const key of conditions) {
    result.push({
      condition: getKeyValue(obj, key),
      count: 1,
    });
  }

  return result;
};

If you are going to be working with a fixed set of strings, you might also be interested in string enums.

  • Related