Home > Software design >  Typescript interface a field type depend on another field value using interface intersection '&
Typescript interface a field type depend on another field value using interface intersection '&

Time:03-19

I'm trying to infer the 'data' type based on the value of a field 'type', but it seems I'm not making it properly... I want to use an intersection operator to avoid many lines of conditions.

See code bellow:

interface IActivityNote {
  type: "note";
  data: {
    title: string;
    content?: string;
  };
}
interface IActivityArchive {
  type: "archive";
  data: { reason: string };
}
type TActivityData = IActivityNote | IActivityArchive;

interface IActivityBase {
  id: string;
  ownerId: string;
}
type TActivity = IActivityBase & TActivityData;

interface IActivityServerBase {
  id: string;
  owner: string;
}
type TActivityServer = IActivityServerBase & TActivityData;

const mapToActivityServer = (activity: TActivity): TActivityServer => ({
  id: activity.id,
  owner: activity.ownerId,
  type: activity.type,
  data: activity.data
});

Why is TS not getting it by itself ? DoI have to put a condition inside the function to specify the data structure based on the type value ?

Reproduction here: https://codesandbox.io/s/typescript-infering-type-value-with-interface-intersection-svdgsd?file=/src/index.ts

Basically I want the type to be either:

{
  id: string
  owner: string
  type: 'note'
  data: { title: string; content?: string }
}

// OR

{
  id: string
  owner: string
  type: 'archive'
  data: { reason: string }
}

But using a smart intersection type to avoid duplicating the entire object types for every new 'type' value with a different 'data' structure that I want to implement.

CodePudding user response:

This is a known bug in TypeScript, see microsoft/TypeScript#33654. Prior to TypeScript 3.5 introduced smarter union type checking there would have been no chance of this working, because the compiler would not bother to try to relate the type it infers for your output with the annotated return type. The PR at microsoft/TypeScript#30779 changed things so that now there is some attempt to expand out object types with union properties into unions of object types, and the PR at microsoft/TypeScript#36663 apparently makes this work better with intersections, but apparently neither of these changes are not enough to make your code work.

It's not clear when or if this bug will be fixed. For now, the workaround suggested in ms/TS#33654 is to spread/destructure so that it does trigger the "smarter union type checking". In your case it could look like this:

const mapToActivityServer = (activity: TActivity): TActivityServer => {
    const { ownerId: owner, ...act } = activity;
    return { ...act, owner }; // no error now
} 

Playground link to code

  • Related