Andrey
Andrey

Reputation: 1496

Resolve __typename union

Say I have this union:

export type IBookmarkItemFragment =
  | ({ __typename: "Story" } & {
      story: number;
    })
  | ({ __typename: "Product" } & {
      product: number;
    })
  | ({ __typename: "Project" } & {
      project: number;
    });

I want .filter and .map based off __typeame. Something like:

bookmarks
  .filter(bookmark => {
    return bookmark.__typename === "Project";
  })
  .map(project => {
    return project.project;
  });

How can I make sure that in the map project will resolve to:

({ __typename: "Project" } & {
  project: number;
})

Upvotes: 1

Views: 865

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249536

You have to explicitly declare the function is a type guard with an annotation:

export type IBookmarkItemFragment =
| ({ __typename: "Story" } & {
    story: number;
    })
| ({ __typename: "Product" } & {
    product: number;
    })
| ({ __typename: "Project" } & {
    project: number;
    });

declare let bookmarks: IBookmarkItemFragment[]
bookmarks
.filter((bookmark):bookmark is Extract<IBookmarkItemFragment, { __typename: "Project"}>   => {
    return bookmark.__typename === "Project";
})
.map(project => {
    return project.project;
});

Or just for fun if we want to generalize the type guard, we can create a guard factory that will work for this any common tagged union.

export type IBookmarkItemFragment =
| ({ __typename: "Story" } & {
    story: number;
    })
| ({ __typename: "Product" } & {
    product: number;
    })
| ({ __typename: "Project" } & {
    project: number;
    });

function guardFactory<T, K extends keyof T, V extends string & T[K]>(k: K, v: V) : (o: T) => o is Extract<T, Record<K, V>> {
    return function (o: T): o is Extract<T, Record<K, V>> {
        return o[k] === v
    }
}
declare let bookmarks: IBookmarkItemFragment[]
bookmarks
.filter(guardFactory("__typename", "Project"))
.map(project => {
    return project.project;
});

Upvotes: 2

Related Questions