#Can I infer record key type from value type?

19 messages · Page 1 of 1 (latest)

digital orbit
#

I'm hesitant to say it's impossible, maybe someone else finds a solution, but I don't think you'll be able to make this one work.

#

For one, you're asking a lot of the type system

#

Secondly, you're running into the correspondence problem

harsh sapphire
#

Yeah I don't think you can do it without casting at all, the problem is that TS allows excess properties so Object.entries is inherently unsafe.

#

And what you are doing is just reflection in other languages.

#

But if you are fine with casting, usually you want to use Object.fromEntries rather than reducing.

magic bobcatBOT
#
nonspicyburrito#0

Preview:```ts
type B = Record<PropertyKey, string | number>

function test<TB extends B>(i: TB) {
const result = Object.fromEntries(
Object.entries(i).map(([key, value]) => [
key,
typeof value === "string"
? new RegExp(value)
: new Date(value)
...```

trail nebula
#

I'm trying to avoid casting. But of course, I'll do that when required

trail nebula
harsh sapphire
#

!:unsafe-keys

magic bobcatBOT
#
retsam19#0
`!retsam19:unsafe-keys`:

Since TS allows objects to have extra properties not specified in the type, it doesn't assume that all the keys on the type are the only keys on the object. This means that Object.keys returns string[] not a specific type, and for(const key in obj), key is string, (not keyof typeof obj).

If you wish to assume otherwise, this utility is often helpful:

// A signature for `Object.keys` that assumes the only keys are the ones indicated by the type
const unsafeKeys = Object.keys as <T>(obj: T) => Array<keyof T>;
harsh sapphire
#

An example:

function doStuff<T extends object>(obj: T) {
    const keys = Object.keys(obj)
}

const fooBarBaz = { foo: 'hello', bar: 'world', baz: 42 }
const fooBar: { foo: string; bar: string } = fooBarBaz

doStuff(fooBar)
#

In the case of passing in fooBar, if you type keys as keyof T then that means it would be 'foo' | 'bar', when in reality it's actually 'foo' | 'bar' | 'baz'.

rough fractal
#

!*:unsafe-keys-example

magic bobcatBOT
#
retsam19#0
`!retsam19:unsafe-keys-example`:

An example where assuming Object.keys(x) returns keyof (typeof x) is unsafe:

type XY = {
   x: string,
   y: string,
}
function louder(obj: XY) {
    Object.keys(obj).forEach(key => {
        // If key is "x" | "y", then this compiles:
        console.log(obj[key].toUpperCase());
    })
}
const obj = { x: "x", y: "y", zero: 0};
louder(obj); // compiles, because extra properties are allowed... but crashes at runtime
trail nebula
#

Make sense. But what am I doing is infer result from input type from user. So, the developers should always prefer pass the correct type info for correct result