Lesik2008
Lesik2008

Reputation: 487

Conditional return type of class method depending on options in constructor

I'm sure this is possible in TypeScript, but the closest resource I could find is this SO question which doesn't seem to work for me.

I have a class that gets options passed in the constructor, and based on that one of its methods should change its return type from string to string[]:

(new MyClass({ multiple: true })).getData(); // returns string[]

(new MyClass({ multiple: false }).getData(); // returns string

What I've tried is to make the options generic and have a conditional return type in getData:

type Options = {
  multiple: boolean;
}

class MyClass<O extends Options> {
  constructor(private options: O) {}

  public getData = (): O["multiple"] extends true ? string[] : string => {
    if (this.options.multiple) {
      return ["foo", "bar"] as string[];
    }

    return "foo" as string;
  };
}

But this gives the error TS2322: Type 'string[]' is not assignable to type 'O["multiple"] extends true ? string[] : string'..

Then I found the SO question linked above and thought that perhaps the "extraction" of O["multiple"] is too complex for TS, so I extracted only the "multiple" field into a generic (instead of the whole options object):

class Day<TReturnsArray extends boolean> {
  constructor(private options: { multiple: TReturnsArray }) {}

  public getData = (): TReturnsArray extends true ? string[] : string => {
    if (this.options.multiple) {
      return ["foo", "bar"] as string[];
    }

    return "foo" as string;
  };
}

But, alas, same error here. I'm not sure why string[] isn't assignable the conditional return type, and the error message doesn't try to "drill down" like other error messages usually do.

Upvotes: 2

Views: 235

Answers (1)

You just need to overload your method:

type Options = {
    multiple: boolean;
}

class MyClass<O extends Options> {
    constructor(private options: O) { }
    public getData(): O["multiple"] extends true ? string[] : string
    public getData() {
        if (this.options.multiple) {
            return ["foo", "bar"];
        }

        return "foo" as string;
    };
}

const stringArray = (new MyClass({ multiple: true })).getData(); // returns string[]

const string = (new MyClass({ multiple: false })).getData(); // returns string

Playground

COnditional type only works/sopported in overloaded signatures

Upvotes: 4

Related Questions