0

I'm trying write a well-typed equivalent of lodash's mapValues which will have a return type with the same shape the original object, not just a Record<x,y>.

Given a source object:

const orig = {
  a: {
    get: () => "foo",
  },
  b: {
    get: () => 5,
  },
  c: {
    get: () => true,
  }
};

Then mapValues(x => x.get(), orig) would return

{
  a: "foo",
  b: 5,
  c: true,
}

And its type would be

{
  a: string,
  b: number,
  c: boolean
}

My first effort is generic on the respective values:

export const mapValues = <V1, V2>(
  f: (v: V1) => V2,
  obj: {[k: string]: V1},
): {[k: string]: V2} => {
  const result: {[k: string]: V2} = {};
  for (const k in obj) {
    result[k] = f(obj[k]);
  }
  return result;
};

But the type ends up being

{
    [k: string]: string | number | boolean;
}

My best effort so far (which doesn't compile) is:

export const mapValues2 = <TInput, TOutput>(
  f: <K extends keyof TInput>(v: TInput[K]) => (K extends keyof TOutput) ? TOutput[K] :never,
  obj: TInput,
): TOutput => {
  const result: Partial<TOutput> = {};
  for (const k in obj) {
    result[k] = f<typeof k>(obj[k]);
  }
  return result as TOutput;
};

I have a playground here.

9
  • 3
    There is a fundamental limitation to what you're trying to do: you are mapping values at runtime which maybe different than the type of an object at compile-time. This is the same reason built-ins like Object.keys doesn't correspond to the type of the object you're reflecting on. If you're doing this for your own use and understand that limitation (i.e. that it isn't actually type-safe) cool, but there's a reason libraries don't. Otherwise use type-safe bespoke access. Commented Apr 22, 2022 at 15:17
  • 1
    It's possible if beforehand you know what operation you want to do (here it's getting the return type of get) like this
    – mochaccino
    Commented Apr 22, 2022 at 15:21
  • @JaredSmith Yeah, I just keep thinking if the correctly-shaped type of the input is known there must be a way to get the corresponding output.
    – N3dst4
    Commented Apr 22, 2022 at 15:22
  • @kellys That's the one! Thank you. Edit - right, it's dependent on the actual operation. Well, that's good progress.
    – N3dst4
    Commented Apr 22, 2022 at 15:23
  • 1
    @JaredSmith Oh that's a good link, thanks. But yeah, you're right. I'll settle for a good old as :)
    – N3dst4
    Commented Apr 22, 2022 at 16:41

0

Browse other questions tagged or ask your own question.