2

I have typed an object that I want to iterate through and do something to each value and then reconstruct the object without losing its typing and want to do this without mutating anything.

This is what I am trying right now:

type myObj = {
    arr1: number
    arr2: number
}

const c: myObj = {arr1: 1, arr2: 1}

const d: myObj = Object.entries(c)
.map(entry => {
    const prop = entry[0]
    const value = entry[1]

    return {[prop]: value} // do something to value here
})

// recombine all the objects into the arrObj structure
.reduce((a, e) => {return {...a, ...e}}, {})

d would have the correct property names and value types and of myObj, yet typescript gives the error

Type '{ [x: string]: number; }' is missing the following properties from type 'myObj': arr1, arr2

How can I modify this?

DevinRa
  • 115
  • 1
  • 7
Lol Lol
  • 75
  • 6
  • 1
    This is really beyond the ability of the TS compiler to check for you. A bunch of issues prevent it: `Object.entries()` is not strongly typed; `{[k]:v}` is not strongly typed where `k` isn't of a single known string literal type; and there's no way the compiler could ever understand that `Object.fromEntries(Object.entries(x))` will produce something more specific than `Partial` since there's no concept of *exhaustive arrays* that scales well. ... – jcalz Sep 06 '22 at 13:50
  • 1
    ... If I work around `Object.entries()` and `{[k]:v}` I get [this](https://tsplay.dev/W4xd1W) where I still have to assert that `Partial` is a full `MyObj`. Does that fully address your question? If so I could write up an answer explaining it, with sources. If not, what am I missing? – jcalz Sep 06 '22 at 13:50
  • Personally I'd say you should just write a helper function like `mapValues()` with type assertions in the implementation and then you use that, as shown [here](https://tsplay.dev/mx3p1N). I could write that up as well or instead. Let me know. – jcalz Sep 06 '22 at 14:01
  • @jcalz that helper function was what i was looking for, thanks. I tried removing as many of the type hints as i could to see what exactly was making TS ok with it - its the casting of the output to the input object type structure. In fact, if you type the input as ```{ [P in K]: any }``` and the final output as ```as { [P in K]: any }``` then the ```Object.entries(input) ... .reduce(...)``` method i used originally actually works. Of course, any is bad here, but i just wanted to remove the types to the bare minimum to make it work. – Lol Lol Sep 06 '22 at 14:54
  • @jcalz One more thing, how do i do this if myObj holds properties of different types? e.g. if ```arr2: string```. If i run ```const e: myObj = mapValues(c, (e)=>e)```, i get the error ```Type '{ arr1: string | number; arr2: string | number; }' is not assignable to type 'myObj'. Types of property 'arr1' are incompatible. Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'.``` – Lol Lol Sep 06 '22 at 15:24
  • Unfortunately that's not just "one more thing", it's a new question. I'm happy to write up my above code as an answer to the current question. Or you can [edit] your question to specifically be asking about the use case where the input/output types have different property types at different keys. (But if you *do* ask that question, the answer will probably be "this is impossible" because TS has no way to represent such transformations at the type level.) What do you want me to do here? – jcalz Sep 06 '22 at 15:28

1 Answers1

0

I tried many things to solve this but came to the conclusion that Object.entries is the problem.

This post describes a workaround to type up Object.entries (also I have just seen jcalz comments): https://stackoverflow.com/a/60142095/4529555

Pay particular attention to this part:

In this case Object.entries would return an array containing the element ['d', false]. The type Entries says this cannot happen, but in fact it can happen; so Entries is not a sound return type for Object.entries in general. You should only use the above solution with Object.entries when you yourself know that the values will have no excess properties; Typescript won't check this for you.

Robert Rendell
  • 1,658
  • 15
  • 18