I built a Generic Type for sort options. Now I am left with two alternatives, which both seem right.
// Option 1
export type SortSearchBy<DtoType> = Record<keyof Partial<Omit<DtoType, 'orderByFields'>>, SortBy>
// Option 2
export type SortSearchBy2<DtoType> = {
[x in keyof Partial<Omit<DtoType, 'orderByFields'>>]: SortBy
}
However the fields of DtoType
are only optional in the second option. For the first option, all fields of DtoType
(execpt for orderByOption
) have to be filled:
class AnimalDto {
name: string
age: number
orderByFields: SortSearchBy<AnimalDto>
}
// here you can('t) set
orderByOptions = {
name: {
option: 'ASC',
order: 1
},
// Error: Type is missing the following properties... (age)
}
class PersonDto {
name: string
age: number
orderByFields: SortSearchBy2<PersonDto> // Notice the 2
}
// while here it is possible
orderByFields = {
name: {
option: 'ASC',
order: 1
}
// No Error
}
Why does this behave differently, when I have used the Partial
generic for both types.
CodePudding user response:
Solution
You are probably not looking for keyof Partial<Omit<DtoType, 'orderByFields'>>
you probably want:
export type SortSearchBy<DtoType> = Partial<Record<keyof Omit<DtoType, 'orderByFields'>, SortBy>>
Reason
Generally keyof T
and keyof Partial<T>
should be the same union of key. After all keyof
just returns the keys of a type regardless of the optionality of the properties.
While the statement above is generally true, mapped types have a special behavior. If you map over keyof T
(ie you have a type like { [P in keyof T]: U }
) where T
is any object type, the mapped type will turn homomorphic, meaning it will preserve the structure (optionality, readonly modifier) of the type T
you are mapping over.
So { [P in keyof Partial<Omit<DtoType, 'orderByFields'>>]: SortBy }
will be a homomorphic mapped type and it will preserve the structure of the type you are mapping. This means the properties in the resulting type will be optional since Partial<Omit<DtoType, 'orderByFields'>>
has all its properties marked as optional
In Record<keyof Partial<Omit<DtoType, 'orderByFields'>>, SortBy>
the mapping happens in Record
and it has no knowledge of were the keys it's mapping over came from, as the keyof
happens before Record
gets a hold of the keys.
CodePudding user response:
In option 1, you are simply extracting the union of keys of the generated record and stating that this union is the accepted keys in your generated record. It doesn't retain their typings (optional, readonly).
In option 2, you are mapping the existing types into a new type. The in
operator checks a to see whether x
is in the union of keys. If it is, it maps that property and retains all type information such as the type being optional.