Typescript: keyof - conditional readonly | optional

Created on 14 Jun 2018  路  3Comments  路  Source: microsoft/TypeScript

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?

Question

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.

/** 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


All 3 comments

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


Was this page helpful?
0 / 5 - 0 ratings

Related issues

rwyborn picture rwyborn  路  210Comments

yortus picture yortus  路  157Comments

fdecampredon picture fdecampredon  路  358Comments

OliverJAsh picture OliverJAsh  路  242Comments

kimamula picture kimamula  路  147Comments