Issue lies within this code snippet:
return { [columnName]: direction }
.
This syntax indicates {[prop: string]: SortDirection}
when the expected result is
Record<keyof T, SortDirection>
Based on my observations, TypeScript struggles with computed object properties. Most of the time, it defaults to an indexed type like {[prop: string]: unknown}
.
To address this issue, I introduced a new function called record
which will output Record<keyof T, unknown>
.
How did I solve this? By adding an additional overload to the function.
Is this solution foolproof? Not entirely, as best practice dictates defining multiple overloads for robustness.
Personally, I find overloading slightly safer than type casting using the as
operator, but only marginally so.
Hence, explicitly specifying the return type of the record
function was necessary.
function record<K extends Keys, V = unknown>(key: K, value: V): { [prop in K]: V }
function record<K extends Keys, V = unknown>(key: K, value: V) {
return { [key]: value }
}
The following alternative notation does not function correctly:
function record<K extends Keys, V = unknown>(key: K, value: V): { [prop in K]: V } {
return { [key]: value } // error
}
Here's a complete example:
interface Cat {
age: number;
breed: string;
}
enum SortDirection {
asc = 'asc',
desc = 'desc',
}
interface Sort<T> {
columnName: keyof T
direction: SortDirection
}
interface CriteriaRequestDto<T> {
sort: Sort<T>
}
type Keys = string | number | symbol;
function record<K extends Keys, V = unknown>(key: K, value: V): { [prop in K]: V }
function record<K extends Keys, V = unknown>(key: K, value: V) {
return { [key]: value }
}
type SortQuery<T> = Partial<Record<keyof T, SortDirection>>
function buildSortQuery<T>(
criteria: CriteriaRequestDto<T>,
): SortQuery<T> {
if (!criteria || !criteria.sort) {
return {}
}
const { columnName, direction } = criteria.sort
return record(columnName, direction)
}
const sortQuery: SortQuery<Cat> = {}
sortQuery.age = SortDirection.asc // OK
const sort: Sort<Cat> = {
columnName: "age", // OK
direction: SortDirection.asc, // OK
}
const criteria: CriteriaRequestDto<Cat> = {
sort: sort //ok
}
const query = buildSortQuery<Cat>(criteria)
query.age
Playground
UPDATE
Please take a look here. Avoid using the as
operator.