undefined
undefined

Reputation: 6844

Typescript function with keyof doesn't accept object key value as valid keyof

I have a simple function that uses the keyof feature:

interface B {
  name: string;
  age: number;
}

function test(key: keyof B) {}

const c = {
  age: 'age'
}

test(c.age);

The issue with the above code is that typescript throws an error that type string is not assignable to keyof B.

So what is the point of the keyof feature if it's not working with object key value? I don't want to add as keyof B.

Upvotes: 1

Views: 774

Answers (3)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249536

The problem is that typescript does not infer by default literal types for object properties (even if they are const).

You can get around this by using a type assertion to the string literal type:

const c = {
  age: 'age' as 'age'
}

test(c.age);

Or of you want to infer string literal types for all properties of an object you can use a function (I use a iffe here but it can be a separate function)

const c = (<V extends string,  T extends Record<string, V>>(o: T) => o)({
  age: 'age'
});

test(c.age);

Or in 3.4 (not yet released) you can use a as const assertion on the object:

const c = {
  age: 'age'
} as const

test(c.age);

Upvotes: 2

Madara&#39;s Ghost
Madara&#39;s Ghost

Reputation: 174957

The reason is that TypeScript infers c to be of type {age: string;}, and not {age: 'age';}.

Had it inferred the latter, instead of the former, you wouldn't have been able to change c.age like so:

const c = { age: 'age' }
c.age = 'somethingElse';

Had you called it with the literal 'age' directly, it would have worked as advertised.

You can override TypeScript inference with a proper type like so:

const c: {age: 'age'} = {
  age: 'age',
};

And that would both make the type error disappear, and enforce that you do not assign anything to c.age other than the literal string 'age'.

Upvotes: 2

Andrei Tătar
Andrei Tătar

Reputation: 8295

c.age will be a string because of the implicit typing (c will be of type {age:string}). You can define the type for c.age to be keyof B. This will also restrict you in assigning other values to the age property and use them accidentaly.

interface B {
  name: string;
  age: number;
}

function test(key: keyof B) { }

const c: { age: keyof B } = {
  age: 'age'
}

test(c.age);

Upvotes: 2

Related Questions