Johann
Johann

Reputation: 187

Type checking object parameter in function using other parameter as its key

I want to create a function that takes in three parameters: an object, a key on the object, and a new value. I want the function to be able to access a value using the key parameter, perform some operation based on that data, and then set the property to the new value.

This was the initial approach I took:

function someOperation(obj: object, key: keyof obj, newValue: typeof obj[key]) 
const obj = { a: 'hello', b: 2 };

// TypeScript should show an error here if the third parameter is not a string
someOperation(obj, 'a', 'goodbye');
console.log(obj); // should be { a: 'goodbye', b: 2 }

Unfortunately, I get some undescriptive errors where I specify the type of newValue. I could just set its type to any to fix the errors, but I really want to have stronger type checking. Does anyone have any ideas on how I could solve this problem? Perhaps there are different ways to approach this. Thanks.

Upvotes: 1

Views: 1204

Answers (2)

Shivam Singla
Shivam Singla

Reputation: 2201

@zixiCat's solution is perfectly valid and works. But it may seem far-fetched and is difficult to debug the error messages like

Argument of type '["a", "1"]' is not assignable to parameter of type 'TParams<{ a: number; b: string; }>'.
  Type '["a", "1"]' is not assignable to type '["a", number]'.
    Type 'string' is not assignable to type 'number'.(2345)

There is more elegant and simple solution possible with "more-friendly" errors.

function someOperation<T extends {}, K extends keyof T>(
  obj: T,
  key: K,
  value: T[K]
) {
  //
}

const test = { a: 1, b: '1' };

someOperation(test, 'a', 1); // OK
someOperation(test, 'b', '1'); // OK
someOperation(test, 'a', '1'); // Error
someOperation(test, 'c', '1'); // Error

Playground

Upvotes: 2

zixiCat
zixiCat

Reputation: 1059

The core idea is union-types and using Arr to wrap them like that:

type paramsArr = [ObjType, 'a', number] | [ObjType, 'b', string]

And then you can try this one:

type TParamsMap<T> = {
  [P in keyof T]: [P, T[P]] // [key, newValue];
};


type TParams<T> = TParamsMap<T>[keyof TParamsMap<T>]

function someOperation<T>(obj: T, ...rest: TParams<T>) {
  obj[rest[0]] = rest[1];
}

const test = { a: 1, b: '1' };

someOperation( test, 'a', 1); // OK
someOperation( test, 'b', '1'); // OK
someOperation( test, 'a', '1'); // Error
someOperation( test, 'c', '1'); // Error

Upvotes: 2

Related Questions