1

I encountered a problem that I simplified by creating the function below :

function setProperty<T extends Record<string, string>>(obj: T, key: keyof T) {
  obj[key] = "hello";
}

The code does not compile, obj[key] is underlined in red and I get the error :

Type 'string' is not assignable to type 'T[keyof T]'.ts(2322)

I understand that it is because of the keyword "extends" but I don't know how to solve my problem.

Can you help me with this ?

3 Answers 3

4

There are two ways to solve your problem depending on your usecase. If you just want to set any string as a value to any particular key, you don't need to use generics:

function setProperty(obj: Record<string, string>, key: string) {
  obj[key] = "hello";
}

However if your object has specific keys and specific values you can use generics like this so the compiler type checks your arguments:

function setProperty<T extends Record<string, string>, K extends keyof T, V extends T[K]>(obj: T, key: K, value: V) {
  obj[key] = value
}

type ObjectValue = 'foo' | 'bar'

type A = {
  foo: ObjectValue,
  baz: 'baz'
}

const a: A = {
  foo: 'foo',
  baz: 'baz'
}

setProperty(a, 'foo', 'bar') // works
setProperty(a, 'foo', 'baz') // type error

This also has a nice benefit of InteliSense sugsestions.

EDIT:

Since OP wanted just to type check the key and set an arbitrary string, this solution should be better:

function setProperty<K extends string>(obj: Record<K, string>, key: K) {
  obj[key] = "hello";
}
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for your answer but it doesn't really solve my problem because my goal here is not to have the value as an argument but to be defined in the function as my code shows.
The first example should work just fine for any obj of type Record<string, string>. If it doesn't work please specify me the type of obj argument that is being passed to the function.
You're right, the first example works but we lose the type checking on "key" argument and I don't want that.
Then you can do this and not lose the key type checking: function setProperty<K extends string>(obj: Record<K, string>, key: K) { obj[key] = "hello"; }
Oh nice ! Perfect one. Can you edit your first answer with this ? Then I can accept it
1

Just use Object.assign

type StringValue<Obj> = {
    [Prop in keyof Obj]: Obj[Prop] extends string ? Prop : never
}[keyof Obj]

/**
 * Obtain all keys with string value
 * Only these keys are allowed
 */
type Result = StringValue<{ age: number, name: string }> // name

function setProperty<
    Value,
    Obj extends Record<string, Value>
>(obj: Obj, key: StringValue<Obj>) {
    Object.assign(obj, { [key]: 'hello' })
}

const user = {
    age: 42,
    name: 'John',
    surname: 'Doe'
}

setProperty(user, 'name') // ok
setProperty(user, 'surname') // ok ok

setProperty(user, 'age') // expected error, because age is a number

With help of StringValue utility type, TS will allow only keys with string value.

Playground

Please try to aboid mutations in TS. See this article.

Comments

1

I'm not sure if this will achieve the result you're looking for with your use case, but if you simply want this to compile, you can use a type assertion.

function setProperty<T extends Record<string, string>>(obj: T, key: keyof T) {
  obj[key] = "hello" as T[keyof T];
}

"hello" has literal type hello. The Typescript compiler can see that you want to set a value on obj, which has keys with type keyof T. It can't infer that type hello is of the required type unless you assert it is as above.

However if you want to set the value on obj[key] to hello, I'd personally use the first approach from Michael Vrana's answer.

1 Comment

Indeed, we can cast the value but I have the feeling that this is not the clean way. "hello" has the literal type "hello" that extends string. That's why we can do const str: string = "hello". Actually obj[key] is not of type string but of a type that extends string and that's why that problem happens I guess.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.