One of the main issues with the current implementation of convertDate
is that it is designed to be generic in the type T
of obj
. In this setup, there is no specific knowledge that T
contains a Date
-valued property at key K
. Additionally, there is no guarantee that keys like `${K}-year`
, `${K}-month`
, or `${K}-day`
exist within T
, making it unsafe to index into obj
with those keys.
By defining the type of obj
in relation to K
based on the key
parameter type, we eliminate the need for an extra generic type parameter. Here is a possible structure:
type DateHavingObj<K extends string | number> =
{ [P in `${K}-${"year" | "month" | "day"}`]: string } &
{ [P in K]?: Date };
This is an intersection of two mapped types. It combines an object type with keys derived from template literal types and corresponding string values, along with an object type containing an optional property with key K
and value Date
.
The updated call signature would look like this:
const convertDate = <K extends string | number>(
obj: DateHavingObj<K>, key: K
) => { }
This function now works properly when called with a MyObjectWithADate
-typed obj
and the right key
:
convertDate(obj, "date"); // behaves as expected
However, it would fail if called with a different key
:
convertDate(obj, "fake"); // results in error
// -------> ~~~
/* Type 'MyObjectWithADate' is missing properties
"fake-year", "fake-month", "fake-day" */
To make sure the convertDate()
implementation compiles without errors, some modifications are needed:
const convertDate = <K extends string | number>(
obj: DateHavingObj<K>, key: K
) => {
const [month, day, year] = [
Number(obj[`${key}-month`]),
Number(obj[`${key}-day`]),
Number(obj[`${key}-year`])
];
const o: { [P in K]?: Date } = obj;
if (month && day && year) {
o[key] = new Date(year, month, day)
}
return obj;
}
The changes in this version include:
Removing unnecessary conversions like String(key)
since key
can be used directly in template literal strings.
Converting the string values obtained from obj
to numbers before using them in the Date
constructor.
Adjusting the assignment by upcasting obj
to the matching type to avoid errors related to generic types.
Verifying the functionality:
console.log(obj.date?.toUTCString()) // "Wed, 11 Jan 2023 06:00:00 GMT"
Everything seems to be working correctly.
Access the code on TypeScript Playground