Hi, I have a question with regards to keyof and if its possible to conditionally change the readonly and optional qualifiers on a property if the properties type extends that of another type. As an example, below I am attempting to map the boolean property Foo.b either readonly or optional leveraging type queries + keyof, along with a few attempts with the syntax to help illustrate the intent.
type Foo = {
a: number
b: boolean // wish to map as b?: boolean
}
// Bar<Foo> = { a: number, b: boolean }
type Bar <T> = { [K in keyof T]: T[K] }
// Bar<Foo> = { a?: number, b?: boolean }
type Bar <T> = { [K in keyof T]?: T[K] }
// (SYNTAX ERROR) Bar<Foo> = { a: number, b?: boolean }
type Bar <T> = { [K in keyof T] : T[K] extends boolean ? (T[K] ? ) : T[K] }
// ^ |
// | |
// +-------- move '?' here ---------+
// (SYNTAX ERROR) Bar<Foo> = { a: number, readonly b: boolean }
type Bar <T> = { [K in keyof T] : T[K] extends boolean ? (readonly T[K]) : T[K] }
// ^ |
// | |
// +---------- move 'readonly' here -------------+
Is it possible to change the readonly and optional qualifiers from the results given from a type query?
You will need to filter the keys of the type first to extract the non-boolean members, then mark these as readonly.
type BooleanPropertyKeys<T> = { [P in keyof T]: T[P] extends boolean ? P : never }[keyof T];
type ReadonlyBoolean<T> = Readonly<Pick<T, Extract<keyof T, BooleanPropertyKeys<T>>>> & Pick<T, Exclude<keyof T, BooleanPropertyKeys<T>>>;
@mhegazy Thank you, that works great! Yes I was looking for a means to do key filtering based on the type and re-combine through intersect, but got stuck with the key filtering, so seeing the syntax forBooleanPropertyKeys is great. I don't think i would have gotten this on my own :)
type BooleanPropertyKeys<T> = { [P in keyof T]: T[P] extends boolean ? P : never }[keyof T];
Thank again.
Solution
The following is the full solution to this issue/question that seems to work well if anyone finds it helpful in future.
/** Include property keys from T where the property is assignable to U */
type IncludePropertyKeys<T, U> = { [P in keyof T]: T[P] extends U ? P : never}[keyof T]
/** Excludes property keys from T where the property is assignable to U */
type ExcludePropertyKeys<T, U> = { [P in keyof T]: T[P] extends U ? never: P}[keyof T]
/** Includes properties from T where the property is assignable to U */
type IncludePropertyTypes<T, U> = { [K in IncludePropertyKeys<T, U>]: T[K] }
/** Excludes properties from T where the property is assignable to U */
type ExcludePropertyTypes<T, U> = { [K in ExcludePropertyKeys<T, U>]: T[K] }
/** Makes properties of type T optional where the property is assignable to U */
type OptionalPropertyType<T, U> = ExcludePropertyTypes<T, U> & Partial<IncludePropertyTypes<T, U>>
/** Makes properties of type T readonly where the property is assignable to U */
type ReadonlyPropertyType<T, U> = ExcludePropertyTypes<T, U> & Readonly<IncludePropertyTypes<T, U>>
interface Foo {
a: boolean, // make optional
b: number, // make readonly
c: string
}
// type Bar = { a?: boolean, readonly b: number, c: string }
type Bar = ExcludePropertyTypes<ExcludePropertyTypes<Foo, number>, boolean> & // exclude numbers and booleans
Partial<IncludePropertyTypes<Foo, boolean>> & // re add booleans as partials
Readonly<IncludePropertyTypes<Foo, number>> // re add numbers as readonly
Most helpful comment
Solution
The following is the full solution to this issue/question that seems to work well if anyone finds it helpful in future.