admin管理员组

文章数量:1398797

Playground

In TypeScript, how do you "assert" that a keyof type is not readonly, when technically it possibly is?

class Foo {
  x = 1
  readonly y = 2
}

const foo = new Foo()

const k = 'x' as keyof Foo

foo[k] = 3

In this case, I know for a fact the key is not readonly, but casting to "any" doesn't solve it. My only solution so far is casting (foo as any)[k] = 3 but surely there's a better way?

Playground

In TypeScript, how do you "assert" that a keyof type is not readonly, when technically it possibly is?

class Foo {
  x = 1
  readonly y = 2
}

const foo = new Foo()

const k = 'x' as keyof Foo

foo[k] = 3

In this case, I know for a fact the key is not readonly, but casting to "any" doesn't solve it. My only solution so far is casting (foo as any)[k] = 3 but surely there's a better way?

Share Improve this question edited Mar 13 at 20:57 jonrsharpe 122k30 gold badges268 silver badges476 bronze badges asked Mar 13 at 20:56 user29889977user29889977 1532 silver badges8 bronze badges 0
Add a comment  | 

2 Answers 2

Reset to default 3

TypeScript features a Readonly utility type, which is designed to mark all Record keys as readonly. To achieve the inverse, we can use the following syntax:

type Writeable<T> = { -readonly [K in keyof T]: T[K] }

Then, we can cast our foo type:

class Foo {
  x = 1
  readonly y = 2
}

const foo = new Foo()

const k = 'x'
(foo as Writeable<Foo>)[k] = 3;

Alternatively, if you want only to get writeable keys for a record:

type EqualMutability<A, B> = (<T>() => T extends A ? true : false) extends
  (<T>() => T extends B ? true : false) ? true : false

type IsReadonly<R, K extends keyof R> = EqualMutability<
  { readonly [S in K]: R[K] },
  { [S in K]: R[K] }
>

type PickWritable<R> = Pick<R, { [K in keyof R]: IsReadonly<R, K> extends false ? K : never }[keyof R]>

const k: keyof PickWritable<Foo> = "x"
foo[k] = 3

You need to narrow your keys to proper writable keys or make the readonly properties to support a runtime check:

Playground

class Foo {
  x = 1
  get y(){ return 2;}
}

const foo = new Foo()

const k = 'x' as keyof Foo


// option 1
if(k === 'x'){ 
    foo[k] = 3
}

// option 2 (you need to use getters)
const canWrite = <T extends object, K extends keyof T>(obj: T, key: K): obj is T & {-readonly[k in K]: T[k]} => {
    const desc = Object.getOwnPropertyDescriptor(obj, key) ?? {};
    if(desc.get && !desc.set) return false;
    if(!desc.set && !desc.writable) return false;
    return true;
}

if(canWrite(foo, k)){
    console.log('can write!');
    foo[k] = 3;
}

if(!canWrite(foo, 'y')){
    console.log('cannot write!');
}

本文标签: How do you cast a potentially readonly key to readwrite key in TypeScriptStack Overflow