The instructions for this challenge are:
Implement the built-in
ReadonlyKeys<T>
generic without using it.Constructs a union of all the keys of
T
that are readonly.
Example code that should compile correctly is provided:
interface Todo {
readonly title: string
readonly description: string
completed: boolean
}
type Keys = GetReadonlyKeys<Todo> // expected to be "title" | "description"
The initial type is:
type GetReadonlyKeys<T> = any
This is the first extreme challenge and my approach takes a few shortcuts:
- Instead of copying the implementation for challenge 4 to provide Pick, using the built-in.
- Using the
Readonly
utility type (not explicitly disallowed in the challenge). We will implement this later anyway. - Using the utility function provided by the type repo
Equal
to handle part of the solution. It would be possible to reimplement this from scratch (I believe it may be a later challenge).
I took these shortcuts because it would be possible to bring in the other types in a modular style later if I should choose so.
The essential principle here follows on from the previous challenges. We want to map the indexes of the type and judge
whether
to include the index in the emitted type depending on whether the key is marked readonly
or not. Another slight
difference is that
we want to emit a union type instead of a filtered object. This latter part is easy as we can simply generate the
filtered
object and then use keyof
on it.
The key realisation that helps solve this problem is that using Pick
and Readonly
(along with Pick
) we can
generate two types that describe an object containing only one of the key -> value pairs from the original object. This
gives
us a few variations:
// original object
type OriginalObject = { property1: string, readonly property2: string };
// pick only - property1
type PickedProperty1 = Pick<OriginalObject, property1> // { property1: string };
// pick and readonly - property1
type PickedReadonlyProperty1 = Readonly<Pick<OriginalObject, property1>> // { readonly property1: string };
// pick only - property2
type PickedProperty2 = Pick<OriginalObject, property2> // { readonly property2: string };
// pick and readonly - property2
type PickedReadonlyProperty2 = Readonly<Pick<OriginalObject, property1>> // { readonly property2: string };
PickedReadonlyProperty2
is the same as PickedProperty2
so all that remains now is to have some means of comparing
the two. The typescript challenge repository
has an Equal
type that allows
two types to be compared (not implementing this myself moves the challenge from extreme
to medium
in my opinion). If
the two types are the same then e.g. Equal<PickedProperty2,PickedReadonlyProperty2>
can only have a value of true
and
we can use a conditional type to determine if this is the case. The normal methods for filtering mapped indexes allows
us
to solve the problem for there onwards.
The eventual solution looks like this:
type GetReadonlyKeys<T> = keyof {
[P in keyof T as Equal<
Pick<T, K>,
Readonly<Pick<T, K>>
> extends true ? P : never]: T[P]
}