Ari Lotter
Ari Lotter

Reputation: 615

Recursively setting a TypeScript type not read-only without clobbering tuples

export type DraftObject<T> = {-readonly [P in keyof T]: Draft<T[P]>}
export interface DraftArray<T> extends Array<Draft<T>> {}
export type Draft<T> = T extends any[]
    ? DraftArray<T[number]>
    : T extends ReadonlyArray<any>
        ? DraftArray<T[number]>
    : T extends object ? DraftObject<T> : T


type tup = [number, number, number, number]

const T: Draft<tup> = [1, 2, 3, 4]
const U: tup = [1, 2, 3, 4]

const TT: tup = T
const UU: Draft<tup> = U

The type DraftObject should return any type with all its properties marked not read-only. This works in all cases, except tuple types, where they're incorrectly turned into arrays. How can I special case tuples, and Readonly<> them instead of making them DraftArrays?

Upvotes: 0

Views: 653

Answers (2)

jcalz
jcalz

Reputation: 329388

If, for some reason, you need to detect the difference between tuples and arrays, you can use the fact that tuple types have a known "0" key (the string value, not the number 0) but arrays do not:

type IfTuple<T extends any[], Y=true, N=false> = "0" extends keyof T ? Y : N

type TestTuple = IfTuple<[string, number], "tuple", "array">; // "tuple"
type TestArray = IfTuple<string[], "tuple", "array">; // "array"

This should be enough to build a conditional type that does something different for tuples from what it does for general arrays.

Hope that helps. Good luck!

Upvotes: 3

Matt McCutchen
Matt McCutchen

Reputation: 30929

As jcalz said, there are no readonly tuples as of TypeScript 3.1. If you want to make everything mutable, you can just use:

export type Draft<T> = {-readonly [P in keyof T]: Draft<T[P]>};

taking advantage of the mapped tuples and arrays added in TypeScript 3.1 and the previously existing special case for homomorphic mapped types applied to primitives (Draft<T> evaluates to T when T is primitive).

Upvotes: 3

Related Questions