Reputation: 387
I tried to use getOwnPropertyDescriptor method but either normal property or readonly property is always writable in return? Do you know any way to check it?
class MyClass {
public readonly id : number = 0
}
const instance = new MyClass()
var itsReadonly = isItReadOnly(instance.id)
Upvotes: 3
Views: 11036
Reputation: 2154
There are two kinds of readonly
-ness when it comes to TypeScript: compile time readonly
-ness and runtime readonly
-ness.
Since there is no code presented in OP, I guess, according to the question, the code is like:
class Foo {
readonly a = 1
b = 2
}
We can define a helper function to check the readonly
-ness of a property of a given object
function isWritable<T extends Object>(obj: T, key: keyof T) {
const desc = Object.getOwnPropertyDescriptor(obj, key) || {}
return Boolean(desc.writable)
}
Then we can check the readonly
-ness:
const foo = new Foo
console.log(isWritable(foo, 'a'), isWritable(foo, 'b'))
// true true
It is printing true true
because foo.a
and foo.b
are both writable at runtime since readonly
keyword in TypeScript does nothing to the emitted JavaScript code.
If we inspect the emitted code of Foo
(targeting ES6):
class Foo {
constructor() {
this.a = 1;
this.b = 2;
}
}
There is no difference between a
and b
at all.
However, if you assign a new value to foo.a
in TypeScript, the type system would complain that a
is a readonly property. This is exactly what the readonly
keyword does: it only provides a compile time constraint but could never affect the runtime behavior.
foo.a = 1 // Cannot assign to 'a' because it is a constant or a read-only property.
foo.b = 3 // correct
If a runtime readonly property is wanted, then the get
syntax could be used.
class Zoo {
get a () { return 1 }
b = 2
}
And emitted code would be:
class Zoo {
constructor() {
this.b = 2;
}
get a() { return 1; }
}
Since the getter exists on the prototype rather than the instance, we need to modify isWritable
function a little bit to cover the getter
.
function isWritable<T extends Object>(obj: T, key: keyof T) {
const desc =
Object.getOwnPropertyDescriptor(obj, key)
|| Object.getOwnPropertyDescriptor(Object.getPrototypeOf(obj), key)
|| {}
return Boolean(desc.writable)
}
Then we check the readonly
-ness again:
const zoo = new Zoo
console.log(isWritable(zoo, 'a'), isWritable(zoo, 'b'))
// false true
Now zoo.a
is readonly both in compile time and run time.
zoo.a = 1
// Type check fails (compile time): Cannot assign to 'a' because it is a constant or a read-only property.
// Throws an error (run time): Uncaught TypeError: Cannot set property a of #<Zoo> which has only a getter
zoo.b = 3 //correct
CAVEAT
The modified isWritable
is still inperfect as it only checks one level upwards along the prototype chain. A complete solution should climb the prototype chain until find a descriptor or reach the end.
Upvotes: 19