Home > Back-end >  Typescript Union on Arrays of Objects
Typescript Union on Arrays of Objects

Time:06-06

Fiddling around with a union of arrays which contains objects. Expected that TS is able to guard the correct type but I guess I am missing smth here:

type A = ReadonlyArray<
  | { readonly __typename: 'EmbedParagraph' }
  | { readonly __typename: 'EntityQueueParagraph' }
  | null
> | null;

type B = ReadonlyArray<
  | { readonly __typename: 'EmbedParagraph' }
  | {
      readonly __typename: 'EntityQueueParagraph';
      readonly id: string | null;
      readonly title: string | null;
    }
  | null
> | null;

function test(paragraphs: A | B) {
  paragraphs?.forEach((paragraph) => {
    /**
     * TS OUTPUT:
     * (parameter) paragraph: {
     *     readonly __typename: "EmbedParagraph";
     * } | {
     *     readonly __typename: "EntityQueueParagraph";
     * } | {
     *     readonly __typename: "EmbedParagraph";
     * } | {
     *     readonly __typename: "EntityQueueParagraph";
     *     readonly id: string | null;
     *     readonly title: string | null;
     * } | null
     */
    if (paragraph?.__typename === 'EntityQueueParagraph') {
      console.log(paragraph);
      /**
       * TS OUTPUT:
       * (parameter) paragraph: {
       *     readonly __typename: "EntityQueueParagraph";
       * }
       */
    }

    if (paragraph && 'id' in paragraph) {
      console.log(paragraph);
      /**
       * TS OUTPUT:
       * (parameter) paragraph: never
       */
    }
  });
}

My thought was, that after the .__typename check, there are both options available. But I am only able to use the entity queue defined in type A. Order does not seem to be the reason.

Same goes for the 2nd condition where I would expect that TS infers type B because the id field is only available there.

Hope you can give me a hint or a further read. Thanks a lot! bw

EDIT
After adding a new key to type As EntityQueueParagraph typing (added foo: number), TS is able to distinguish the two types... unfortunately not usable for my situation.

CodePudding user response:

Apparently with a map, instead of forEach you already have better inheritance. And changing from paragraphs?. to paragraphs && paragraphs. when checking the __typename fixed your issue.

type A = ReadonlyArray<
  | { readonly __typename: 'EmbedParagraph' }
  | { readonly __typename: 'EntityQueueParagraph' }
  | null
> | null;

type B = ReadonlyArray<
  | { readonly __typename: 'EmbedParagraph' }
  | {
      readonly __typename: 'EntityQueueParagraph';
      readonly id: string | null;
      readonly title: string | null;
    }
  | null
> | null;

function test(paragraphs: A | B) {
  paragraphs?.map((paragraph) => {
    if (paragraph && paragraph.__typename === 'EntityQueueParagraph') {
      console.log(paragraph);
    }

    if (paragraph && 'id' in paragraph) {
      console.log(paragraph);
    }
  });
}
  • Related