title: Firebase Generic withConverter Typescript
body: <p>I'm using firebase to interact with firstore and am taking advantage of the &quot;withConverter&quot; method to handle the mapping of data to and from my domain objects. I want to try and eliminate having to write custom withConverter logic for all my domain objects but keep getting typescript errors when trying to create a generic version. I'm not sure it's even possible.</p>
  <p>I want to do something like</p>
  <pre><code>export function collectionWithConverter&lt;T,D&gt;(TCreator:{new ():T},ref: CollectionReference&lt;DocumentData&gt;): CollectionReference&lt;T&gt; {
    return ref.withConverter({
      toFirestore: (obj: T) =&gt; {
        return obj.toFirestoreJSON()
      },
      fromFirestore: (snapshot: QueryDocumentSnapshot, options: SnapshotOptions) =&gt; {
        const data = snapshot.data(options) as D
        return new TCreator({...data, &quot;id&quot;: snapshot.id})
      }})
  }
  </code></pre>
  <p>But I get errors:
  'data' is declared but its value is never read.ts(6133)
  Type 'CollectionReference' is not assignable to type 'CollectionReference'.
  Type 'DocumentData' is not assignable to type 'T'.
  'T' could be instantiated with an arbitrary type which could be unrelated to 'DocumentData'</p>
  <pre><code>export enum ItemCollectionFields {
    ID = &quot;id&quot;,
    ITEM_ID = &quot;itemID&quot;,
    NAME = &quot;name&quot;,
    DESCRIPTION = &quot;descriptionText&quot;,
  }
  
  const ICF = ItemCollectionFields
  
  export interface ItemProps {
    [ICF.ID]: string,
    [ICF.ITEM_ID]: string,
    [ICF.NAME]: string,
    [ICF.DESCRIPTION]: string|null,
  }
  
  export class Item {
    id: string|null = null
    name: string|null = null
    descriptionText: string|null = &quot;&quot;
    constructor(props:Partial&lt;ItemProps&gt;) {
      this[ICF.ID] = props[ICF.ID]
      this[ICF.NAME] = props[ICF.NAME]
      this[ICF.DESCRIPTION] = props[ICF.DESCRIPTION]
    }
    toJSON(): ItemProps {
      return {
        [ICF.ID]: this[ICF.ID],
        [ICF.NAME]: this[ICF.NAME],
        [ICF.DESCRIPTION]: this[ICF.DESCRIPTION],
      }
    }
  
    toFirestoreJSON(): Partial&lt;ItemProps&gt; {
      const {id, ...rest} = this.toJSON()
      for (const key in rest) {
        if (rest[key] === &quot;&quot; || rest[key] === null || rest[key] === undefined) {
          delete rest[key]
        }
      }
      return rest
    }
  
    static defaultItem():Item {
      return new Item({id: &quot;&quot;, name: &quot;&quot;, descriptionText: &quot;&quot;})
    }
  }
  ------------------------------------------------------------------------------------
  
  export function getCollectionReference&lt;T&gt;(collectionName:Collections, converter: (ref: CollectionReference&lt;DocumentData&gt;)=&gt;CollectionReference&lt;T&gt;): CollectionReference&lt;T&gt; {
    return converter(firestoreAccessor(collectionName))
  }
  
  export function getDocumentReference&lt;T&gt;(collectionName:Collections, itemID:string, converter: (ref: DocumentReference&lt;DocumentData&gt;)=&gt;DocumentReference&lt;T&gt;): DocumentReference&lt;T&gt; {
    return converter(doc(getFirestore(), collectionName, itemID))
  }
  
  export function collectionWithConverter(ref: CollectionReference&lt;DocumentData&gt;): CollectionReference&lt;Item&gt; {
    return ref.withConverter({
      toFirestore: (Item: Item) =&gt; {
        return Item.toFirestoreJSON()
      },
      fromFirestore: (snapshot: QueryDocumentSnapshot, options: SnapshotOptions) =&gt; {
        const data = snapshot.data(options) as ItemProps
        return new Item({...data, &quot;id&quot;: snapshot.id, &quot;itemID&quot;: snapshot.id})
      }})
  }
  
  export function documentWithConverter(ref: DocumentReference&lt;DocumentData&gt;): DocumentReference&lt;Item&gt; {
    return ref.withConverter({
      toFirestore: (Item: Item) =&gt; {
        return Item.toFirestoreJSON()
      },
      fromFirestore: (snapshot: QueryDocumentSnapshot, options: SnapshotOptions) =&gt; {
        const data = snapshot.data(options) as ItemProps
        return new Item({...data, &quot;id&quot;: snapshot.id, &quot;itemID&quot;: snapshot.id})
      }})
  }
  ------------------------------------------------------------------------------------
  #Example Use case
  export async function getItem(itemID: string): Promise&lt;Item|null&gt; {
    const ref = getDocumentReference&lt;Item&gt;(Collections.ITEMS, itemID, documentWithConverter)
    const snap = await getDoc(ref)
  
    if (snap.exists) {
      return snap.data()
    }
    return null
  }
  </code></pre>