I'm writing a function which updates property in one object with some other property from another object
like
function compareAndUpdate<T, S>(target: T, source: S, targrtKey: keyof T, sourceKey: keyof S): boolean {
// some processing
target[targrtKey] = source[sourceKey]; // this line gives error
// some more processing
}
I know that I can fix error by using <any>
target and source are of distinct types. T and S may or may not have proprieties with same name , but T can have a property whose type is same as some other property in S
Is there a way to guaranty that typeof target[targrtKey] === typeof source[sourceKey] so that compiler catch error when incompatible property keys are passed.
Edit: Submitted upstream feature request here
You can, in fact, do this. Here's how:
function compareAndUpdate<T, TK extends keyof T, SK extends string, S extends Record<SK,T[TK]>>(target: T, source: S, targetKey: TK, sourceKey: SK): boolean {
// some processing
target[targetKey] = source[sourceKey]; // okay
return true;
}
Let's unpack that declaration:
T is the type of the targetTK is the type of the target key (so it needs to extend keyof T). Note that T[TK] is the type of the target value for that key.SK is the type of the source key (so it needs to extend string)S is the type of source, which needs to have SK as a key, and any value you get from that key needs to be assignable to T[TK]. Record<SK,T[TK]> is a type with keys of type SK, and values of type T[TK]. So when S extends Record<SK,T[TK]>, we're saying that we want S to be any type where S[SK] is the same as T[TK].Let's try it:
interface Person {
name: string;
age: number;
}
const person: Person = { name: 'Stephen King', age: 69 };
interface Book {
title: string;
authorName: string;
}
const book: Book = { title: 'The Running Man', authorName: 'Richard Bachman' };
compareAndUpdate(book, person, 'authorName', 'name'); // okay
compareAndUpdate(book, person, 'authorName', 'age'); // error, number not assignable to string
compareAndUpdate(person, book, 'authorName', 'name'); // error, 'name' property not found
Does that work for you?
You could just use the same type if you want them to be equal :
function compareAndUpdate<T>(target: T, source: T, targrtKey: keyof T, sourceKey: keyof T): boolean { return true; }
Another version would be to guarantee that no property of S is not present in T
function compareAndUpdate<T extends S, S>(target: T, source: S, targrtKey: keyof T, sourceKey: keyof S): boolean { return true; }
When you use you will get errors if someone tries to put on the target, properties not part of T
//Valid
compareAndUpdate({ test: "S"}, { test: "DD" }, "test", "test")
compareAndUpdate({ test: "S", bla : 0}, { test: "DD" }, "test", "test")
//Invalied, bla does not exist on T
compareAndUpdate({ test: "S"}, { test: "DD", bla : 0 }, "test", "test")
I use object literals for simplicity, but it will work with classes as well, if S contains fields in the class :
class Dog {
name: string;
bark(): void{}
}
// OK
compareAndUpdate(new Dog(), {name: "" }, "name", "name");
// NOK
compareAndUpdate(new Dog(), {isAGodBoy: "" }, "name", "name");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With