Home > Net >  How can I make Typescript require the value of a property to be set, before another property can be
How can I make Typescript require the value of a property to be set, before another property can be

Time:12-25

I need to define a type to accept only objects that can have one property defined with a value if another property in the same object has a value defined.

I tried the following type definition for my requirement and it is the closest that I have from what I want.

type TypeA = {
  width?: number;
  height?: number;
  position: string;
  innerSize?: string;
} | {
  width?: number;
  height?: number;
  position?: undefined;
}

The function that receives an object with that type needs to check that property position is defined before being able to use innerSize, that is cool.

However, when creating objects of this type, I have something unexpected happening:

function test() {
  const a: TypeA = {};
  const b: TypeA = { width: 50, height: 75 };
  const c: TypeA = { position: "5, 7" };

  // Type '{ innerSize: string; }' is not assignable to type 'TypeA'.
  // Property 'position' is missing in type '{ innerSize: string; }' but required in type '{ width?: number | undefined; height?: number | undefined; position: string; innerSize?: string | undefined; }'.
  const d: TypeA = { innerSize: "400, 120" };

  const e: TypeA = { width: 50, innerSize: "400, 120" }; // It compiles!
}

See that I cannot create the variable d because Typescript requires the property position to be defined. However, I can bypass that restriction just by defining the property width... so what gives?

CodePudding user response:

{ width: 50, innerSize: "400, 120" }

This object is technically compatible with TypeA, since it matches with this part of the union:

{
  width?: number;
  height?: number;
  position?: undefined;
}

It has a width which is a number, and it omits both height an position, so it matches the type. It does have an excess property, innerSize, but typescript allows excess properties when checking for compatibility.

Still, that's not a full explanation, because normally typescript checks for excess properties on the line where you create the object, and so it should catch this. It should, but it doesn't, because this is a known (and longstanding) bug: https://github.com/microsoft/TypeScript/issues/20863

CodePudding user response:

As for a solution, use the never type for properties that must not be defined in some shapes:

type TypeA = {
    width?: number;
    height?: number;
    position: string;
    innerSize?: string;
} | {
    width?: number;
    height?: number;
    position?: undefined;
    innerSize?: never;
}

function test() {
    const a: TypeA = {};
    const b: TypeA = { width: 50, height: 75 };
    const c: TypeA = { position: "5, 7" };
    const c2: TypeA = { position: "5, 7", innerSize: "400, 120" }; // Okay

    // Type '{ innerSize: string; }' is not assignable to type 'TypeA'.
    // Property 'position' is missing in type '{ innerSize: string; }' but required in type '{ width?: number | undefined; height?: number | undefined; position: string; innerSize?: string | undefined; }'.
    const d: TypeA = { innerSize: "400, 120" };
    //    ~

    const e: TypeA = { width: 50, innerSize: "400, 120" }; // Error: Property 'position' is missing in type '{ width: number; innerSize: string; }' but required in type '{ width?: number | undefined; height?: number | undefined; position: string; innerSize?: string | undefined; }'.
    //    ~
}

Playground Link

  • Related