0

I created a function that takes a specific array of objects and returns a copy of this array but with changed key names and values.

I cannot figure out how to get rid if this one any in the code below and still get this function to work.

If I set result type to ResultType I get a whole bunch of errors, i.e

  • Type '{}' is not assignable to type 'ResultType'. '{}' is assignable to the constraint of type 'ResultType', but 'ResultType' could be instantiated with a different subtype of constraint 'ObjectType'.
  • Type 'string' cannot be used to index type 'ResultType'.
  • Type 'string' is not assignable to type 'ResultType[Extract<keyof InputType, string>]'.
type TMap<T> = Map<T, number | string>
type ObjectType = Record<string, any>

export const mapArrayOfObjectsKeysAndValues = <InputType extends ObjectType, ResultType extends ObjectType>(
  objectArray: InputType[],
  keyMap: TMap<string>,
  valueMap: TMap<InputType[keyof InputType]>,
): ResultType[] => {
  return objectArray.map(object => {
    const result: any = {} // I would like to get rid of this one any

    for (const key in object) {
      const mappedValue = valueMap.get(object[key])
      const mappedKey = keyMap.get(key)
      switch (true) {
        case !!(mappedValue && mappedKey):
          result[mappedKey] = mappedValue
          break
        case !!(!mappedValue && mappedKey):
          result[mappedKey] = object[key]
          break
        case !!(mappedValue && !mappedKey):
          result[key] = mappedValue
          break
        default:
          result[key] = object[key]
      }
    }
    return result
  })
}

Playground link

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
rseven
  • 1
  • 1
    I sincerely doubt you'll be able to implement this without needing some type assertions or other type loosening. But your call signature isn't type safe, so the errors are correct. The *caller* chooses `ResultType`, not the implementer, so nothing stops someone from calling [this](https://tsplay.dev/m0nARw) and the compiler claims that the returned array is of some type it can't actually supply. – jcalz Oct 13 '22 at 14:41
  • 1
    If you're willing to always output `ObjectType[]` then you can do so without `any` like [this](https://tsplay.dev/wEBRZN). If you want something more specific than that, you should probably describe how you'd like that type to be determined and then we can talk about how to implement it as safely as possible (which might not be very safe) – jcalz Oct 13 '22 at 14:50
  • @jcalz Thank you for replies. I like how you dropped switch statement in above solution so I used it in my solution. I came up with this [link](https://tsplay.dev/ND22lW). I used type assertion for result type `const result = {} as ResultType`. And looks like Object.assign method get rids of indexing errors. I really need to keep those generic input and result types so I can make sure that all data is mapped properly in my React application. – rseven Oct 14 '22 at 15:16
  • But you're [not making sure the data is mapped properly](https://tsplay.dev/m0nnRw). You can use a type assertion if you want, but then you must be fully aware that nothing stops someone from lying to the compiler. Does that make sense? It's fine if you're okay with that, but you should acknowledge it. So, would you like an answer written up for this question? Or am I missing something about your use case? – jcalz Oct 14 '22 at 15:23
  • And note, you can get a bit closer to call-side type safety with something like [this](https://tsplay.dev/wEBBZN) that at least *tries* to figure out what the maps will do to the input. `Map` is such a wide type that it will probably produce weird things in a lot of cases... if you have `Map([["a", "z"],["b", "y"]])` all the compiler will know is that `a` becomes either `z` or `y`, because `Map` doesn't associate *particular* values with *particular* keys unless you do [this](https://stackoverflow.com/questions/54907009). I think there's no good way to be safe with this implementation. – jcalz Oct 14 '22 at 15:32
  • @jcalz It turned out that my solution was not good at all so thank you for enlightening me here. Let me summarize once again what I would like to achieve in the end and maybe we can come up with a better solution. I want to be able to: - explicitly define object type that comes in and comes out of the function (so I think we need generics here) - change some of the keys names or values of provided objects - use neither any nor type assertion - I am ok with dropping Map completely and replacing it with a normal object library if it makes it better and type-safe – rseven Oct 15 '22 at 17:43
  • I think you might want to [edit] your question to lay out your actual requirements as explicitly as possible, because potentially the scope is now quite wide. You should probably present a specific use case (input type, conversion strategy, resulting output type) that demonstrates all the important edge conditions that you're likely to run into. If you can't do that, then I don't know how to advise other than to throw some general stuff at you and hope something sticks. – jcalz Oct 15 '22 at 19:10
  • I'm particularly confused about any conversion strategy that converts values without caring about keys. Keys are generally strings and can be compared with `===`, but values can be all sorts of things, so even identifying which values to convert seems difficult. – jcalz Oct 15 '22 at 19:17
  • Like [here](https://tsplay.dev/wj1X6W) is something fairly general but I don't know if it meets your needs. Oh and finally it is essentially impossible to "use neither any nor type assertion" inside the function implementations; TS doesn't have the ability to follow the sort of generic conditional logic necessary to verify that an implementation conforms to the annotated return type. The best we can do is come up with something where *callers* don't need to use type assertions, most of the time, anyway. – jcalz Oct 15 '22 at 19:28
  • Or maybe [this](https://tsplay.dev/NB5Jdw)? Just wondering. – jcalz Oct 16 '22 at 20:40

0 Answers0