I have a certain interface that aligned with this:
interface Example {
field_a: number | null;
field_b: readonly string[];
readonly field_c: string;
nested: {
field_d: readonly unknown[]
}
}
I want to apply a mutation and remove all readonly, something along like this:
type Mutable<T> = {
-readonly [K in keyof T]: T[K] extends Readonly<T[K]> ? Mutable<T[K]> : T[K];
};
Creating a new type Mutable<Example>
, will apply Mutate to all fields, even when it's not needed, so if I hover on field_a
, the type applied is Mutable<number | null>
, which is not what I want.
I need the final the result to be as shown below for this particular example (when doing Mutable<Example>
), but should be dynamic enough to handle different interfaces.
type TransformedExample = {
field_a: number | null;
field_b: string[];
field_c: string;
nested: {
field_d: unknown[]
}
}
Thanks in advance for the help!
It sounds like you only want to iterate the keys of object types, and not other types.
If so, then you just need one more conditional check for that in the form of T extends object
:
type Mutable<T> =
T extends object
? {
-readonly [K in keyof T]:
T[K] extends Readonly<T[K]>
? Mutable<T[K]>
: T[K];
}
: T
WHich works like you expect:
interface Example {
field_a: number | null;
field_b: readonly string[];
readonly field_c: string;
nested: {
field_d: readonly unknown[]
}
}
type TransformedExample = Mutable<Example>
And TransformedType
is reported as:
type TransformedExample = {
field_a: number | null;
field_b: string[];
field_c: string;
nested: {
field_d: unknown[];
};
}
Which is nice and clean.
In fact, you can remove the T[K] extends Readonly<T[K]>
check entirely now which simplifies this a lot.
type Mutable<T> =
T extends object
? { -readonly [K in keyof T]: Mutable<T[K]> }
: T
Since you want every object property mutable, you don't have to check if it's read only first. You just make everything mutable, regardless of how it started.