Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to write a function that modifies a given property of an object where the property must be a certain type?

What I want to do:

type Apple = {
  seeds: number,
  size: number
};

const apple = {
  seeds: 5,
  size: 2
};

double(apple, 'seeds');
double(apple, 'size');

console.log(apple); // => { seeds: 10, size: 4 }

This is trivial in JS:

function double(object, property) {
  object[property] *= 2;
}

In TS it’s an absolute nightmare. This obviously doesn’t work:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

function double<T>(object: T, property: keyof T) {
  object[property] *= 2;
}

The left-hand side of an arithmetic operation must be of type ‘any’, ‘number’, ‘bigint’ or an enum type

I had high hopes of this working, but… no luck:

function double<T extends { [k in K]: number }, K extends keyof T>(object: T, property: K) {
  object[property] *= 2;
}

Type ‘number’ is not assignable to type ‘T[K]’.
‘number’ is assignable to the constraint of type ‘T[K]’, but ‘T[K]’ could be instantiated with a different subtype of constraint ‘number’.

I think it’s odd that TS should enforce this when it won’t do so for non-generics, but so be it. Any better ideas?

>Solution :

There are a couple of options here:

First. No need to define a generic for an object, only define K and make object parameter be of type Record<K, number>:

function double<K extends string>(object: Record<K, number>, property: K) {
  object[property] *= 2;
}

double(apple, 'seeds');
double(apple, 'size');
double(apple, 'invalid'); // error

The downside of this approach is that there are no autosuggestions for the property

Second. Define two generics T and K extends keyof T, however, we won’t type object as T but rather just a Record<K, number>:


function double<T, K extends keyof T>(object: Record<K, number>, property: K) {
  object[property] *= 2;
}

double(apple, 'seeds'); // autosuggestions working
double(apple, 'incorrect'); // error

The advantage is that autosuggestions are now working. The compiler is able to infer the type for T the because we have typed object as Record<K, number>, and since K extends keyof T, object is T. The downside of this method is that object is required to have only number fields, otherwise the compiler will raise an error, since K will be a union of all keys, meaning that all fields of T are numbers.

playground

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading