Search code examples
typescripttypescript-genericsconditional-types

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.


Solution

  • 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