title: Generic type of flatten nested value objects
body: <p>I'm trying to define generic types for domain/aggregate and entities that would enable me to build aggregates that include a number of domains. All in all this would lead to nested value object classes.</p>
  <p>In order to simplify API response I would like to provide a toObject() function that would return a somewhat flatten object with strings (or better yet; ValueType).</p>
  <p>In the example below I would like order.toObject() to return the type and object as specified in the end.</p>
  <p>I would also like to be able to define a recursive/deep generic type for BaseDomain.toObject()</p>
  <p>Any ideas? I welcome feedback and thoughts</p>
  <p><a href="https://tsplay.dev/w16P8N" rel="nofollow noreferrer">Playground</a></p>
  <pre><code>//// What I would like to solve /////
  
  // Define a generic type that flatten a object by removing all class specific and only keeping the properties
  type FlattenProperties&lt;T&gt; = any;
  
  // My poor attempt
  /*
  type FlattenProperties&lt;T&gt; = T extends IBaseEntity&lt;infer U&gt; ? U
      : T extends IBaseList&lt;infer U&gt;
          ? T['items'] extends IBaseDomain&lt;infer Z&gt;[] ? [...T['items']]: FlattenRecursiveEntityProperties&lt;Z&gt;  //Not sure of how to recurse iterate a array 
          : U[]
      : T extends object ? {[K in keyof T]: FlattenRecursiveEntityProperties&lt;T[K]&gt;}
      : T;
  
  */
  
  //// Helpers //////
  
  interface IValue&lt;ValueType&gt; {
      value: ValueType
  }
  
  interface IBaseEntity&lt;ValueType&gt; {
      value: ValueType
  }
  
  abstract class BaseEntity&lt;ValueType&gt; implements IBaseEntity&lt;ValueType&gt; {
      readonly properties: IValue&lt;ValueType&gt;;
      constructor(value?: ValueType) {
          this.properties = { value: value ?? {} as ValueType };
      }
      get value(): ValueType {
          return this.properties.value;
      }
  
  }
  
  class ObjectId extends BaseEntity&lt;string&gt; {
      constructor(id?: string) {
          super()
          this.properties.value = id ?? 'GeneratedID';
      }
  }
  
  interface IBaseList&lt;ItemType&gt; {
      items: ItemType[]
  }
  abstract class BaseList&lt;ItemType&gt; {
      readonly items: ItemType[];
      constructor(items?: ItemType[]) {
          this.items = items ?? [];
      }
      getItems() {
          return this.items;
      }
      add(item: ItemType) {
          this.items.push(item);
      }
  }
  
  interface IBaseDomain&lt;Interface&gt; {
      readonly properties: Interface;
      readonly _id: ObjectId;
      toObject(): FlattenProperties&lt;Interface&gt;
  }
  
  abstract class BaseDomain&lt;Interface&gt; implements IBaseDomain&lt;Interface&gt; {
      readonly properties: Interface;
      readonly _id: ObjectId;
      constructor(object?: Interface, id?: ObjectId) {
          this._id = id ?? new ObjectId('#a-generated-value#');
          this.properties = object ?? {} as Interface;
      }
      get id() {
          return this._id
      }
      toObject(): FlattenProperties&lt;Interface&gt; {
          return {} as FlattenProperties&lt;Interface&gt;;
      }
  
  }
  
  
  
  ///// Definition of domains ///////
  
  interface IProduct {
      name: string;
      description: string
  }
  
  class Product extends BaseDomain&lt;IProduct&gt; {
      constructor(name: string, description: string) {
          super({ name: name, description: description })
      }
  }
  
  interface IOrderLine {
      product: Product;
      qty: number;
  }
  
  class OrderLine extends BaseDomain&lt;IOrderLine&gt; {
      constructor(product: Product, qty: number) {
          super({ product: product, qty: qty })
      }
  }
  
  class OrderLines extends BaseList&lt;OrderLine&gt; { }
  
  interface IOrder {
      orderComment: string;
      orderLines: OrderLines
  }
  
  class Order extends BaseDomain&lt;IOrder&gt; {
  
  }
  
  ///// Example run /////
  
  const toyTruck = new Product('Toy truck', 'Yellow plastic toy truck');
  const toyDuck = new Product('Duck', 'Made famous by Duck Sauce');
  
  export const order = new Order({
      orderComment: 'Some new shiny toys',
      orderLines: new OrderLines([new OrderLine(toyTruck, 1), new OrderLine(toyDuck, 100)])
  });
  
  const htmlResponse: Expected = order.toObject(); // Type = Expected
  htmlResponse.orderLines[0].product.name; // Type = string
  
  type Expected = {
      orderComment: string,
      orderLines: {
          product: {
              name: string,
              description: string
          },
          qty: number
      }[]
  }
  
  type Expected2 = {
      orderComment: string,
      orderLines: {
          product: IProduct,
          qty: number
      }[]
  }
  
  declare var x: Expected;
  declare var x: Expected2;
  //declare var x: FlattenProperties&lt;IOrder&gt;; //TODO: Make valid
  </code></pre>