1

Let's say I have a function

function addField(obj, name, value) {
    return { ...obj, [name]: value }
}

How do I type it in Typescript? EDIT: I'm having trouble with the return type. I'd like it to be "the original T but with this one [name] property in it".

I hoped this could work, but unfortunately it doesn't:

function addField<T, V>(obj: T, name: string, value: V): T & { `${name}`: V } {
    return { ...obj, [name]: value }
}

(it complains that name is a value and not a type). So, a kind of hack that works is

// Hope that nameVal has only one key
function addField<T, V, N extends { [K: string]: V }>(
  obj: T,
  nameVal: N
): T & { [K in keyof N]: V } {
  return { ...obj, ...nameVal };
}

But this still isn't 100% what I want (as it allows for multiple fields being added), and it's not really nice either. Is there a better way to dynamically add fields to an object and codify it into the type systen?

EDIT2: I guess I could use this to disallow objects with more than one key, but I'd like to keep the original addField(obj, name, value) anyway.

Eugleo
  • 418
  • 4
  • 11
  • You don't need the return type, typescript can infer it. [Playground example](https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABAQwCaoGIwKYBtUA8AKgDSIDSAfABRwBGAVgFyKmJjIC22iLAzlABOMMAHMyAN2S4QPFuQCUiAN4AoRBsSDsUEIKTLEAOhP0GZDtxZSZPAL6qHq5xAQDEZxAF4U6LHlRqQxhUFgBmRDsyAHJLbGiYgAUdeIUAblVXMHczACZvX0wcfCDEEPDImLiExABGdNUgA) –  Nov 27 '20 at 13:46
  • @MikeS. My bad, I had a typo in the two topmost examples. I want the field name to be dynamically provided as a parameter. In that case, TS doesn't infer the type automatically (or rather, not granularly enough) – Eugleo Nov 27 '20 at 13:49
  • Ah, so just about [this](https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABAQwCaoGIwKYBtUA8AKgDSIDSi2AHlNmKgM6IDeiA2gNbYCeiAXIkZQATjDABzALqDwnMHADuSAL4A+ABRwARgCtBpRADdkuENgEUAlAcQAyCqwBQiV4hHYoIEUjYA6AJ1dMgC-EzMLFQBuJxUgA)? The only problem with it is that it allows duplicate keys, which or may not be in your intention –  Nov 27 '20 at 14:08
  • @MikeS. I think you accidentally sent me the same file. I only see `function addField(obj: T, name : string, value : K) { ... }`. – Eugleo Nov 27 '20 at 14:13
  • Weird, its different for me. Ill just paste it here `function addField(obj: T, value : K): T & K { return { ...obj, ...value }; }` You don't need the 3rd type parameter, because typescript should be able to infer it from usage. As for the multiple keys thing, the 2nd parameter will override the fields, which again, may or may not be your intention (it won't be duplicates though) –  Nov 27 '20 at 14:15
  • @MikeS. Ok thanks. So, you think there isn't a way _not_ to have `[name]` as a property name of another object? – Eugleo Nov 27 '20 at 14:28
  • Now that I think about it, of course there isn't. The types are erased during compile and don't see runtime values. – Eugleo Nov 27 '20 at 14:52
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/225213/discussion-between-eugleo-and-mike-s). – Eugleo Nov 27 '20 at 15:05

1 Answers1

1

this is a normal problem which often happens to me. Simply, TypeScript IntelliSense isn't perfect and Microsoft knows it. Therefore TypeScript enables typing as unknown as [your type] but be careful with that, that's not good practice like usage !important in CSS. So back to your problem with the trick you can simply use:

function addField<T, V, N extends string>(object: T, name: N, value: V) {
    return {
        ...object,
        [name]: value
    } as unknown as T & {
        [K in N]: V
    };
}

It works fine: IntelliSense

Tomáš Wróbel
  • 658
  • 3
  • 12
  • 1
    Díky — uh, I mean, thanks. Not as nice as I would have hoped, but probably the best we can do at the moment. – Eugleo Nov 29 '20 at 13:03
  • Just one thing — could you please explain why the `unkown` is necessary here? (mostly for the people that will read this in the future) – Eugleo Nov 29 '20 at 13:09
  • 1
    Therefore are compatible types (Like a parent and derived class) and incompatible (like number and string). You can use `as` only with two compatible types. (In an example, they are incompatible). `any` and `unknown` are wild card which are compatible with all types. See https://www.typescriptlang.org/docs/handbook/type-compatibility.html for more info. – Tomáš Wróbel Dec 01 '20 at 15:40