I have some big object, like
const a={ b:33, c:[78, 99], d:{e:{f:{g:true, h:{boom:'selecta'}}}};/// well, even deeper than this...
And I'd like TS not to allow me to do
a.d.e.f.h.boom='respek';
How can I immutate the object completely? Is it only by creating interface with "readonly" and interfaces for each deeply nested object?
Immutability using the readonly keyword. The const keyword is a JavaScript keyword which means you can make a variable immutable natively. However, TypeScript provides a readonly keyword that can be used as a compile-time check to avoid mutation of object properties, class properties, array, etc.
Immutable objects are thread-safe so you will not have any synchronization issues. Immutable objects are good Map keys and Set elements, since these typically do not change once created. Immutability makes it easier to parallelize your program as there are no conflicts among objects.
But strings in JavaScript are different. They are immutable primitives. This means that the characters within them may not be changed and that any operations on strings actually create new strings.
The immutable objects are objects whose value can not be changed after initialization. We can not change anything once the object is created. For example, primitive objects such as int, long, float, double, all legacy classes, Wrapper class, String class, etc. In a nutshell, immutable means unmodified or unchangeable.
We now have the option as const
which is a syntactically concise way of what @phil294 mentioned as the first option (nested readonly
).
const a = { b: 33, c: [78, 99], d:{e:{f:{g:true, h:{boom:'selecta'}}}} } as const; a.d.e.f.h.boom = 'respek'; //Cannot assign to 'boom' because it is a read-only property.ts(2540)
As an added bonus, you can make inputs to functions nested immutable using this trick:
type Immutable<T> = { readonly [K in keyof T]: Immutable<T[K]>; }
so this would happen
const a = { b: 33, c: [78, 99], d:{e:{f:{g:true, h:{boom:'selecta'}}}} } function mutateImmutable(input: Immutable<typeof a>) { input.d.e.f.h.boom = 'respek'; //Cannot assign to 'boom' because it is a read-only property.ts(2540) }
As described in https://www.typescriptlang.org/docs/handbook/interfaces.html, you can use readonly
on class/interface properties or Readonly<...>
/ReadonlyArray<>
for immutable objects and arrays. In your case, this would look like the following:
const a: Readonly<{ b: number, c: ReadonlyArray<number>, d: Readonly<{ e: Readonly<{ f: Readonly<{ g: boolean, h: Readonly<{ boom: string }> }> }> }> }> = { b: 33, c: [78, 99], d:{e:{f:{g:true, h:{boom:'selecta'}}}} } a.d.e.f.h.boom = 'respek'; // error: Cannot assign to 'boom' because it is a constant or a read-only property.
Obviously, this is quite the tautological statement, so I suggest you define proper class structure for your object. You are not really taking advantage of any of Typescript's features by just declaring a nested, untyped object.
But if you really need to go without type definitions, I think the only way is defining a freezer (love the term :D) like Hampus suggested. Taken from deepFreeze(obj)
function from MDN:
function freezer(obj) { Object.getOwnPropertyNames(obj).forEach(name => { if (typeof obj[name] == 'object' && obj[name] !== null) freezer(obj[name]); }); return Object.freeze(obj); } const a = freezer({ b:33, c:[78, 99], d:{e:{f:{g:true, h:{boom:'selecta'}}}}}); a.d.e.f.h.boom='respek'; // this does NOT throw an error. it simply does not override the value.
tl;dr: You cannot get compiler type errors without defining types. That is the whole point of Typescript.
edit:
this very last statement is wrong. For example,
let a = 1 a = "hello"
will throw an error because the type is implicitly set to number. For readonly however, I think, you will need proper declaration as defined above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With