Petr D. Svoboda
Petr D. Svoboda

Reputation: 45

Strong types for a function getting a value from object

I have a function for getting value by key from an object that allows me to see suggestions of possible keys in record when using it. The function infers types from it's arguments.

function get<T extends Record<string, any>, K1 extends keyof T>(
    record: T | undefined,
    key1: K1
): T[K1] | undefined

const foo = { bar: 1, baz: 2 }
get(foo, 'bar') // 1, get<{ a: number, b: number }>

Is it possible to convert it to point-free style, keep the strong types and don't lose suggestions? Something similiar to this.

function get<T extends Record<string, any>, K1 extends keyof T>(key1: K1):
    (record: T | undefined) => T[K1] | undefined

const foo = { bar: 1, baz: 2 }
get('bar')(foo) // 1, get<{ a: number, b: number }>

Obviously this doesn't work. I tried multiple variants of the pointfree function, but I couldn't get it to work. I looked at ramda implementation of function prop, which works pointfree, but it doesn't provide suggestions as it allows any string as key.

type prop = <P extends string>(key: P) => <T>(obj: Record<P, T>) => T

const foo = { bar: 1, baz: 2 }
prop('bar')(foo) // 1, prop: <"bar">(key: "bar") => <T>(obj: Record<"bar", T>) => T

EDIT:

Just to be clear I know I wouldn't be able get suggestions without specifying the record first.

prop('...') // no suggestions here
prop('...')(foo) // now I want suggestions

Upvotes: 1

Views: 281

Answers (3)

gillyspy
gillyspy

Reputation: 1598

If you define your function as I have done here then it is possible.

For you something like:

interface GetFunction<T extends { [K: string]: any }={ [K: string]: any }> {
 <K extends keyof T>(record: T, key1: K1 ): T[K1];
}
export const get : GetFunction;

to use it you would pass the specific kev-value based type if you have it (the default on the generic means you do not have to do this:

const foo = {bar: 1, baz: "2"}; 
const get2 = get as GetFunction<typeof foo>
const answer: 1 = get2( foo, 'bar'); //'bar'|'baz' will be suggestions

Here is a playground link of something similar (which is also on the linked question ) Playground

Upvotes: 0

Qwertiy
Qwertiy

Reputation: 21440

Here is it (works even in typescript 3.3, have no older versions to check):

function get<K extends keyof any>(key: K): <T extends { [key in K]: any }>(x: T) => T[K] {
    return x => x[key]
}

const foo = { bar: 1, baz: "2" }

const x = get('bar')(foo) // number
const y = get('baz')(foo) // string

const z = get('oops')(foo) // Argument of type '{ bar: number; baz: string; }' is not assignable to parameter of type '{ oops: any; }'

Opps. Missed the point about autosuggestions. They don't work in this way as supposed to work left to right in most cases and this one is not exception.

Upvotes: 0

brunnerh
brunnerh

Reputation: 185140

I highly suspect that this is impossible as the order of the dependencies is reversed. If the object were to be supplied first that would not be an issue.

Right now you would have to specify the type of the object manually via the generics, e.g.:

const get = <T extends Record<K1, any>, K1 extends keyof T>(key1: K1) =>
    (record: T) => record[key1];

const foo = { bar: 1, baz: 2 }
get<typeof foo, keyof typeof foo>('bar')(foo)

Upvotes: 2

Related Questions