Daniele Ricci
Daniele Ricci

Reputation: 15837

TypeScript conditional type resolution and optional properties

What I'm try to obtain:

(I use an example to be more quick and clear)

// a function func which works with all following tests
function func<...>(opts: { inOpt?: boolean }): ... {}

// test suite 1
const t1 = func({ inOpt: false });
t1.outOpt = 0; // OK if lecit
t1.outOpt = "0"; // OK if error
t1.outOpt = undefined; // OPTIONAL OK if error

// test suite 2
const t2 = func({ inOpt: true });
t2.outOpt = 0; // OK if error
t2.outOpt = "0"; // OK if lecit
t2.outOpt = undefined; // OPTIONAL OK if error

// test suite 3
const t3 = func({});
t3.outOpt = 0; // OK if lecit
t3.outOpt = "0"; // OK if error
t3.outOpt = undefined; // OPTIONAL OK if error

What I've tried:

I did several attempts, the function declarations most close to what I need are:

// this makes test suites 1 and 2 to work
function func1<T extends boolean>(opts: { inOpt?: T }): { outOpt?: T extends true ? string : number } {
    return {};
}
// this makes test suit 3 to work
function func5<T extends boolean, R extends { inOpt?: T }>(opts: R): { outOpt?: R extends { inOpt: T } ? (T extends true ? string : number) : number } {
    return {};
}

I would have expected that simply func1 to work: if T is not specified it doesn't extends true.

What I need is quite close to the example explained in the official TypeScript documentation, but the ? to make the inOpt optional. What I need more to the official TypeScript documentation example is that when inOpt is not specified it is treated as if it was false, but after several attempts I feel I'm getting stuck.

Here is the Playgroung I used for my tests/attempts.

Edit:

Looking at the first two comments I received I try to explain by words what I need.

I need a function which takes as argument an object with an optional inOpt attribute of type boolean and returns an object with an optional outOpt attribute of type depending on inOpt value. The requirements is that the outOpt attribute must be:

Upvotes: 2

Views: 654

Answers (1)

You can try next example:


// You can try simple function overloading
function foo(arg: { inOpt: true }): string;
function foo(arg: { inOpt: false }): number;
function foo(arg: {}): number;
function foo<T,>(arg: T) {
    return arg
}

const result = foo({inOpt:true}) // string
const result2 = foo({inOpt:false}) // number
const result3 = foo({}) // number

UPDATE Without function overloading:

type InputTrue = { inOpt: true }
type InputFalse = { inOpt: false }
type InputNone = {}
type Inputs = InputTrue | InputFalse | InputNone


type Infer<T> = T extends { inOpt: infer R } ? R : false;
type Mapped<T> = T extends true ? { outOpt: string } : T extends false ? { outOpt: number } : never;

type Result<T> = Mapped<Infer<T>>;

function foo<T extends Inputs>(arg: T): Result<T> {
    return arg as any as Result<T> // implementation is purposely skipped
}

const result = foo({ inOpt: true }) // string
const result2 = foo({ inOpt: false }) // number
const result3 = foo({}) // number

Upvotes: 1

Related Questions