I'd like to compare and assign certain fields of two objects, let's say:
const left = { a: 1, ignored: 5, something: 7 };
const right = { a: 2, ignored: 6, else: 8, id: 18 };
And I want to call leftToRight(left, right, ['a'])
after which right
should be:
{ a: 1, ignored: 6, id: 18 }
and I want to call some other function and pass right.id, which I know does exists on the second argument.
My current approach is:
leftToRight(left, right, keys) {
let changed = false;
for (const key of keys) {
if (!Object.is(left[key], right[key])) {
right[key] = left[key];
changed = true;
}
}
doSomething(right.id)
return changed
}
I'm struggling to find the appropriate type definition :-(
Initial approach:
leftToRight<T>(left: T, right: T, keys: Array<keyof T>): boolean
leads to: "Property 'id' does not exist on type 'T'" and I found no way to check this ('id' in right
)
Second attempt:
leftToRight<T>(left: T, right: T & {id: number}, keys: Array<keyof T>): boolean
leads to "Type 'T' is not assignable to type '{ id: number; }'" for right[key] = left[key]
Third attempt:
leftToRight<T, U extends {id: number}>(left: T, right: U, keys: Array<keyof T & keyof U>): boolean
again leads to an error for the assignment right[key] = left[key]
because the types T and U could be completely unrelated.
Edit:
You have clarified your requirements to say that Left
and Right
might each have properties which are not present in the other. To handle this scenario, we need two generic types.
Right
is an object which includes an id
. We want the selected keys to be present in both Left
and Right
and we want them to have the same value types.
I defined the generic Keys
as any subset of the keys of Right
. I defined Left
as the subset of Right
containing all these keys.
function leftToRight<Right extends {id: number}, Keys extends keyof Right>(
left: Pick<Right, Keys>, right: Right, keys: Keys[]
) {
let changed = false;
for (const key of keys) {
if (!Object.is(left[key], right[key])) {
right[key] = left[key];
changed = true;
}
}
doSomething(right.id)
return changed
}
Original Answer:
I tried a few things before finding one that works. Of course this is not the cleanest definition. I got errors when using the generic to describe Left
and adding id
to get Right
, but removing from Right
works.
We say that Right
is an object with an {id: number}
property and Left
must have all properties of Right
except for id
. In order for the elements of keys
to be present on both objects, we need to ignore the key id
from keyof Right
.
function leftToRight<Right extends {id: number}>(
left: Omit<Right, 'id'>, right: Right, keys: (Exclude<keyof Right, 'id'>)[]
) {
let changed = false;
for (const key of keys) {
if (!Object.is(left[key], right[key])) {
right[key] = left[key];
changed = true;
}
}
doSomething(right.id)
return changed
}