I am currently working on a function that updates nested values within a record. I have different versions of this function for paths of varying depths.
My main struggle is figuring out how to properly type the callback function used when updating the value.
interface Test {
foo?: { bar: number }
}
const input: Test = { foo: { bar: 1 } }
update(input, 'foo', 'bar')(v => v + 1)
Upon using the function, I encounter an error stating that "Object(v) is of type unknown".
Interestingly, I have another similar function called set which is defined similarly, but it receives proper typing when used like this:
set(input, 'foo', 'bar')(2)
Here's the update function implementation:
type UpdateFn<T> = (value: T) => T
export function update<T extends Record<string, any>, K1 extends keyof T>(
record: T | undefined,
key1: K1
): (callback: UpdateFn<NonNullable<T[K1]>>) => T
export function update<
T extends Record<string, any>,
K1 extends keyof T,
K2 extends keyof NonNullable<T[K1]>
>(
record: T | undefined,
key1: K1,
key2: K2
): (callback: UpdateFn<NonNullable<T[K1][K2]>>) => T
export function update<
T extends Record<string, any>,
K1 extends keyof T,
K2 extends keyof NonNullable<T[K1]>
>(
record: T | undefined,
key1: K1,
key2?: K2
): (
callback:
| UpdateFn<NonNullable<T[K1]>>
| UpdateFn<NonNullable<T[K1][K2]>>
) => T | undefined {
return callback => {
if (record === undefined) return record
if (key2 === undefined) {
const value = get(record, key1)
if (value === undefined) return record
return set(record, key1)(callback(value))
} else {
const value = get(record, key1, key2)
if (value === undefined) return record
return set(record, key1, key2)(callback(value))
}
}
}
Set (working correctly):
export function set<
T extends Record<string, any>,
K1 extends keyof T,
K2 extends keyof NonNullable<T[K1]>
>(record: T | undefined, key1: K1, key2: K2): (value: T[K1][K2]) => T