title: Typescript readonly object literals
body: <p>I have a complex data model in which I need to define let's say 40-50 singleton "metadata" objects that:</p>
  
  <ul>
  <li>use Discriminated Unions</li>
  <li>have some regular fields but also a Dictionary which can contain an arbitrary number of objects belonging to a Discriminated Union</li>
  </ul>
  
  <p>I would like to be able to keep the implicit type created for the object literal so that I can manipulate those 40-50 objects in a type-safe manner with intellisense.</p>
  
  <p>Here is an example using the Shapes from <a href="http://www.typescriptlang.org/docs/handbook/advanced-types.html" rel="noreferrer">typescript's documentation for Advanced Types</a>:</p>
  
  <pre class="lang-js prettyprint-override"><code>interface Square {
      kind: "square";
      size: number;
  }
  interface Rectangle {
      kind: "rectangle";
      width: number;
      height: number;
  }
  interface Circle {
      kind: "circle";
      radius: number;
  }
  type Shape = Square | Rectangle | Circle;
  type Shapes = { [x: string]: Shape };
  
  interface Canvas { 
      name: string;
      size: number;
      //etc
  }
  interface Drawing extends Canvas {
      shapes: Shapes;
  }
  
  //compiles but does not give me intellisense on drawingExplicitType.shapes because it is a Dictionary 
  let drawingExplicitType: Drawing = {
      name: 'myDrawing',
      size: 100,
      shapes: {
          c: { kind: "circle", radius: 1 },
          r: { kind: "rectangle", width: 1, height: 1 },
      }
  };
  
  //compiles and gives me intellisense on drawingImplicitType.shapes
  let drawingImplicitType = {
      name: 'myDrawing',
      size: 100,
      shapes: {
          c: { kind: "circle", radius: 1 },
          r: { kind: "rectangle", width: 1, height: 1 },
      }
  };
  //will have many objects like drawingImplicitType (around 40-50) which I want to manipulate in a type safe manner
  //also the shapes will have sub-shapes, so it's a tree-like data model
  
  //I still want to be able to put all "drawing*" objects in an array of Drawing(s)
  //ERROR!!! does not compile: Type 'string' is not assignable to type '"circle"'.
  let drawing: Drawing = drawingImplicitType;
  
  
  //## workaround ##########################################
  
  class Drawing2 implements Canvas {
      name: string;
      size: number;
      shapes: Shapes;
  
      fromObjLiteral&lt;T extends Pick&lt;Drawing2, Exclude&lt;keyof Drawing2, 'shapes' | 'fromObjLiteral'&gt;&gt; &amp; {shapes: any}&gt;(
          obj: T &amp; {shapes: {readonly [x in keyof T['shapes']]: Shape}}): Drawing2 
      {
          Object.assign(this, obj);
          return this;
      }
  }
  
  //compiles and gives me intellisense on drawingImplicitType2.shapes
  let drawingImplicitType2 = new (class {
      name = 'myDrawing';
      size = 100;
      shapes = new (class {
          c = new (class { readonly kind = "circle"; radius = 1 })();
          r = new (class { readonly kind = "rectangle"; width = 1; height = 1 })();
      })();
  })();
  
  let drawing2: Drawing2 = new Drawing2().fromObjLiteral(drawingImplicitType2);
  let array: Drawing2[] = [drawing2];
  //The workaround compiles and still allows me to make arrays of Drawing2(s) 
  //But I have to create new objects and the syntax is super ugly compared to:
  //let drawingImplicitType2 = readonly {...obj literal here...}
  </code></pre>
  
  <p><strong>EDIT 2018-05-25</strong></p>
  
  <p>As <a href="https://stackoverflow.com/users/1113002/aleksey-l">Aleksey L.</a> pointed out type assertions would be simpler to use, however we need to use them on all sub-objects to get strong type safety. This can be a bit annoying with a recursive tree-like data model where <code>Shape</code>s can contain other <code>Shape</code>s but I think it's better than <code>new (class {...})()</code></p>
  
  <pre class="lang-js prettyprint-override"><code>//type assertions compile but allow me to make all kinds of errors:
  let drawingTypeAssertion = {
      name: 'myDrawing',
      size: 100,
      nonExistentField: 123,
      shapes: {
          c: { kind: "circle", radiusWrongField: 1 },
          r: { kind: "rectangle", width: 1, height: 1 },
      }
  };
  let drawingTA: Drawing = drawingImplicitType as Drawing;
  
  //#####################################################################
  // workaround 2: deep type assertions
  //#####################################################################
  
  let drawingDeepTypeAssertion = {
      name: 'myDrawing',
      size: 100,
      nonExistentField: 123,
      shapes: {
          c: { kind: "circle", radius: 1 } as Shape,
          r: { kind: "rectangle", width: 1, height: 1 } as Shape,
      }
  };
  let drawingDTA: Drawing = drawingImplicitType as Drawing;
  let array2: Drawing[] = [drawingDTA];
  //This workaround compiles and still allows me to make arrays of Drawing2(s)
  //no need to create extra objects and the syntax is a bit nicer than new (class {...})() 
  </code></pre>
  
  <p><em>END EDIT 2018-05-25</em></p>
  
  <p>One big downside of <em>workaround 2</em> is that as soon as you do <code>as Shape</code> you lose the implicit type and go-to-definition in vscode.</p>
  
  <p>As I understand it, the problem comes from the fact that the object literals are mutable and the discriminated unions will not work. Mainly because the mutability causes the type of <em>discriminant</em> field <code>kind</code> to be <code>string</code> instead of <code>'square'</code> or <code>'rectangle'</code>.</p>
  
  <p>There is much discussion about deep readonly and "const":
  <a href="https://github.com/Microsoft/TypeScript/issues/10725" rel="noreferrer">https://github.com/Microsoft/TypeScript/issues/10725</a>
  <a href="https://github.com/Microsoft/TypeScript/issues/15300" rel="noreferrer">https://github.com/Microsoft/TypeScript/issues/15300</a></p>
  
  <p>Is there a better workaround with the current version of typescript than the ones above?</p>
  
  <p><a href="http://www.typescriptlang.org/play/#src=interface%20Square%20%7B%0D%0A%20%20%20%20kind%3A%20%22square%22%3B%0D%0A%20%20%20%20size%3A%20number%3B%0D%0A%7D%0D%0Ainterface%20Rectangle%20%7B%0D%0A%20%20%20%20kind%3A%20%22rectangle%22%3B%0D%0A%20%20%20%20width%3A%20number%3B%0D%0A%20%20%20%20height%3A%20number%3B%0D%0A%7D%0D%0Ainterface%20Circle%20%7B%0D%0A%20%20%20%20kind%3A%20%22circle%22%3B%0D%0A%20%20%20%20radius%3A%20number%3B%0D%0A%7D%0D%0Atype%20Shape%20%3D%20Square%20%7C%20Rectangle%20%7C%20Circle%3B%0D%0Atype%20Shapes%20%3D%20%7B%20%5Bx%3A%20string%5D%3A%20Shape%20%7D%3B%0D%0A%0D%0Ainterface%20Canvas%20%7B%20%0D%0A%20%20%20%20name%3A%20string%3B%0D%0A%20%20%20%20size%3A%20number%3B%0D%0A%20%20%20%20%2F%2Fetc%0D%0A%7D%0D%0Ainterface%20Drawing%20extends%20Canvas%20%7B%0D%0A%20%20%20%20shapes%3A%20Shapes%3B%0D%0A%7D%0D%0A%0D%0A%2F%2Fcompiles%20but%20does%20not%20give%20me%20intellisense%20on%20drawingExplicitType.shapes%20because%20it%20is%20a%20Dictionary%20%0D%0Alet%20drawingExplicitType%3A%20Drawing%20%3D%20%7B%0D%0A%20%20%20%20name%3A%20&#39;myDrawing&#39;%2C%0D%0A%20%20%20%20size%3A%20100%2C%0D%0A%20%20%20%20shapes%3A%20%7B%0D%0A%20%20%20%20%20%20%20%20c%3A%20%7B%20kind%3A%20%22circle%22%2C%20radius%3A%201%20%7D%2C%0D%0A%20%20%20%20%20%20%20%20r%3A%20%7B%20kind%3A%20%22rectangle%22%2C%20width%3A%201%2C%20height%3A%201%20%7D%2C%0D%0A%20%20%20%20%7D%0D%0A%7D%3B%0D%0A%0D%0A%2F%2Fcompiles%20and%20gives%20me%20intellisense%20on%20drawingImplicitType.shapes%0D%0Alet%20drawingImplicitType%20%3D%20%7B%0D%0A%20%20%20%20name%3A%20&#39;myDrawing&#39;%2C%0D%0A%20%20%20%20size%3A%20100%2C%0D%0A%20%20%20%20shapes%3A%20%7B%0D%0A%20%20%20%20%20%20%20%20c%3A%20%7B%20kind%3A%20%22circle%22%2C%20radius%3A%201%20%7D%2C%0D%0A%20%20%20%20%20%20%20%20r%3A%20%7B%20kind%3A%20%22rectangle%22%2C%20width%3A%201%2C%20height%3A%201%20%7D%2C%0D%0A%20%20%20%20%7D%0D%0A%7D%3B%0D%0A%2F%2Fwill%20have%20many%20objects%20like%20drawingImplicitType%20(around%2040-50)%20which%20I%20want%20to%20manipulate%20in%20a%20type%20safe%20manner%0D%0A%2F%2Falso%20the%20shapes%20will%20have%20sub-shapes%2C%20so%20it&#39;s%20a%20tree-like%20data%20model%0D%0A%0D%0A%2F%2FI%20still%20want%20to%20be%20able%20to%20put%20all%20%22drawing*%22%20objects%20in%20an%20array%20of%20Drawing(s)%0D%0A%2F%2FERROR!!!%20does%20not%20compile%3A%20Type%20&#39;string&#39;%20is%20not%20assignable%20to%20type%20&#39;%22circle%22&#39;.%0D%0Alet%20drawing%3A%20Drawing%20%3D%20drawingImplicitType%3B%0D%0A%0D%0A%0D%0A%2F%2Ftype%20assertions%20compile%20but%20allow%20me%20to%20make%20all%20kinds%20of%20errors%3A%0D%0Alet%20drawingTypeAssertion%20%3D%20%7B%0D%0A%20%20%20%20name%3A%20&#39;myDrawing&#39;%2C%0D%0A%20%20%20%20size%3A%20100%2C%0D%0A%20%20%20%20nonExistentField%3A%20123%2C%0D%0A%20%20%20%20shapes%3A%20%7B%0D%0A%20%20%20%20%20%20%20%20c%3A%20%7B%20kind%3A%20%22circle%22%2C%20radiusWrongField%3A%201%20%7D%2C%0D%0A%20%20%20%20%20%20%20%20r%3A%20%7B%20kind%3A%20%22rectangle%22%2C%20width%3A%201%2C%20height%3A%201%20%7D%2C%0D%0A%20%20%20%20%7D%0D%0A%7D%3B%0D%0Alet%20drawingTA%3A%20Drawing%20%3D%20drawingImplicitType%20as%20Drawing%3B%0D%0A%0D%0A%0D%0A%2F%2F%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%0D%0A%2F%2F%20workaround%201%3A%20anonymous%20class%20instancess%0D%0A%2F%2F%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%0D%0A%0D%0Aclass%20Drawing2%20implements%20Canvas%20%7B%0D%0A%20%20%20%20name%3A%20string%3B%0D%0A%20%20%20%20size%3A%20number%3B%0D%0A%20%20%20%20shapes%3A%20Shapes%3B%0D%0A%0D%0A%20%20%20%20fromObjLiteral%3CT%20extends%20Pick%3CDrawing2%2C%20Exclude%3Ckeyof%20Drawing2%2C%20&#39;shapes&#39;%20%7C%20&#39;fromObjLiteral&#39;%3E%3E%20%26%20%7Bshapes%3A%20any%7D%3E(%0D%0A%20%20%20%20%20%20%20%20obj%3A%20T%20%26%20%7Bshapes%3A%20%7Breadonly%20%5Bx%20in%20keyof%20T%5B&#39;shapes&#39;%5D%5D%3A%20Shape%7D%7D)%3A%20Drawing2%20%0D%0A%20%20%20%20%7B%0D%0A%20%20%20%20%20%20%20%20Object.assign(this%2C%20obj)%3B%0D%0A%20%20%20%20%20%20%20%20return%20this%3B%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0A%0D%0A%2F%2Fcompiles%20and%20gives%20me%20intellisense%20on%20drawingImplicitType2.shapes%0D%0Alet%20drawingImplicitType2%20%3D%20new%20(class%20%7B%0D%0A%20%20%20%20name%20%3D%20&#39;myDrawing&#39;%3B%0D%0A%20%20%20%20size%20%3D%20100%3B%0D%0A%20%20%20%20shapes%20%3D%20new%20(class%20%7B%0D%0A%20%20%20%20%20%20%20%20c%20%3D%20new%20(class%20%7B%20readonly%20kind%20%3D%20%22circle%22%3B%20radius%20%3D%201%20%7D)()%3B%0D%0A%20%20%20%20%20%20%20%20r%20%3D%20new%20(class%20%7B%20readonly%20kind%20%3D%20%22rectangle%22%3B%20width%20%3D%201%3B%20height%20%3D%201%20%7D)()%3B%0D%0A%20%20%20%20%7D)()%3B%0D%0A%7D)()%3B%0D%0A%0D%0Alet%20drawing2%3A%20Drawing2%20%3D%20new%20Drawing2().fromObjLiteral(drawingImplicitType2)%3B%0D%0Alet%20array%3A%20Drawing2%5B%5D%20%3D%20%5Bdrawing2%5D%3B%0D%0A%2F%2FThis%20workaround%20compiles%20and%20still%20allows%20me%20to%20make%20arrays%20of%20Drawing2(s)%20%0D%0A%2F%2FBut%20I%20have%20to%20create%20new%20objects%20and%20the%20syntax%20is%20super%20ugly%20compared%20to%3A%0D%0A%2F%2Flet%20drawingImplicitType2%20%3D%20readonly%20%7B...obj%20literal%20here...%7D%0D%0A%0D%0A%2F%2F%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%0D%0A%2F%2F%20workaround%202%3A%20deep%20type%20assertions%0D%0A%2F%2F%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%23%0D%0A%0D%0Alet%20drawingDeepTypeAssertion%20%3D%20%7B%0D%0A%20%20%20%20name%3A%20&#39;myDrawing&#39;%2C%0D%0A%20%20%20%20size%3A%20100%2C%0D%0A%20%20%20%20nonExistentField%3A%20123%2C%0D%0A%20%20%20%20shapes%3A%20%7B%0D%0A%20%20%20%20%20%20%20%20c%3A%20%7B%20kind%3A%20%22circle%22%2C%20radius%3A%201%20%7D%20as%20Shape%2C%0D%0A%20%20%20%20%20%20%20%20r%3A%20%7B%20kind%3A%20%22rectangle%22%2C%20width%3A%201%2C%20height%3A%201%20%7D%20as%20Shape%2C%0D%0A%20%20%20%20%7D%0D%0A%7D%3B%0D%0Alet%20drawingDTA%3A%20Drawing%20%3D%20drawingImplicitType%20as%20Drawing%3B%0D%0Alet%20array2%3A%20Drawing%5B%5D%20%3D%20%5BdrawingDTA%5D%3B%0D%0A%2F%2FThis%20workaround%20compiles%20and%20still%20allows%20me%20to%20make%20arrays%20of%20Drawing2(s)%0D%0A%2F%2Fno%20need%20to%20create%20extra%20objects%20and%20the%20syntax%20is%20a%20bit%20nicer%20than%20new%20(class%20%7B...%7D)()%20%0D%0A" rel="noreferrer">Link to typescript playground</a>.</p>