What's the correct way to refer to types of property name and value pair of an object?

Suppose I have an object with properties of different types. Is there a way in TypeScript to have a generic function that performs an operation on a given key and value of an object?

Here's an example. I have an object s with three fields of different types:

interface Struct {
  a: string
  b: boolean
  c: string
}

const s: Struct = {
  a: 'hello',
  b: false,
  c: 'world',
}

Suppose I have a function that does something with one of the properties:

function makeSetter<K, V>(name: K): (value: V) => void {
  return (value) => s[name] = value;
}

How should I parametrize types K and V so that for makeSetter('a') I would get a function of type (value: string) => void and for makeSetter('b') I would receive a function of type (value: boolean) => void?

Upvotes: 2

Views: 73

Answers (2)

Shaun Luttin
Shaun Luttin

Reputation: 141652

The approach by Gerrit0 is useful. Here is a second useful approach (that is also in the playground).

function makeSetter<K extends keyof Struct>(name: K): (value: Struct[K]) => void {
  return value => (s[name] = value);
}

const setter1 = makeSetter("a")("foo");
const setter2 = makeSetter("b")("foo"); // error
const setter3 = makeSetter("c")(false);

keyof T is the index type query operator and T[K] is the indexed access operator. Both are described under the heading Index Types in the Advanced Types docs.

Upvotes: 0

Gerrit0
Gerrit0

Reputation: 9242

The trick here is to use generic constraints. K must be a keyof Struct and V must be assignable to Struct[K]. TypeScript docs

interface Struct {
  a: string
  b: boolean
  c: string
}

const s: Struct = {
  a: 'hello',
  b: false,
  c: 'world',
}

function makeSetter<K extends keyof Struct, V extends Struct[K]>(name: K): (value: V) => void {
  return (value) => s[name] = value;
}

// Helpers for type "unit tests"
type Equal<T, U> = [T] extends [U] ? [U] extends [T] ? true : false : false
function assertTrue<T extends true>() {}

const a = makeSetter('a')
const b = makeSetter('b')

assertTrue<Equal<typeof a, (value: string) => void>>() // No error
assertTrue<Equal<typeof b, (value: boolean) => void>>() // No error

Upvotes: 2

Related Questions