Osama Qarem
Osama Qarem

Reputation: 1459

Object with conditionally optional keys

function getColors(
    url: string,
    config: Config
  ): Promise<Result>;
}

type Config = {
  dominant?: boolean;
  average?: boolean;
};

type Result = {
  dominant?: string;
  average?: string;
};

How can I say if dominant was passed as true to the config object e.g. getColors(url, { dominant: true}) then it is not optional in Result.

async () => {
  let example : string;

  const colors = await getColors(url, {dominant: true});
  example = colors.dominant;
                 // ^^^^^^ Type 'string | undefined' is not assignable to type 'string'.
                 //        Type 'undefined' is not assignable to type 'string'
};

Upvotes: 3

Views: 482

Answers (2)

Maciej Sikora
Maciej Sikora

Reputation: 20162

We can do that by conditional types. Consider:

type Config = {
  dominant?: boolean;
  average?: boolean;
};

type Result = {
  dominant?: string;
  average?: string;
};

declare function getColors<C extends Config>(
    url: string,
    config: C
): Promise<Result & (C['dominant'] extends true ? {dominant: string} : {})>;


async function f() {
  const colors = await getColors('url', { dominant: true })
  colors.dominant // is non-optional string
}

The core thing is to have Config as generic in getColors and append conditional behavior in the return type: Result & (C['dominant'] extends true ? {dominant: string} : {}). What we do here is - if dominant is true in config, we intersect with our type object having dominant required, if dominant is false we intersect with {} which is neutral element, so we get Result back.

Playground

Upvotes: 4

kaya3
kaya3

Reputation: 51162

You can provide two signatures by overloading the function:

interface Config { average?: boolean, dominant?: boolean }
interface ConfigDominant extends Config { dominant: true }

interface Result { average?: string, dominant?: string }
interface ResultDominant extends Result { dominant: string }

// more specific signature must go first
function getColors(url: string, config: ConfigDominant): Promise<ResultDominant>;
// less specific signature
function getColors(url: string, config: Config): Promise<Result>;
// implementation
function getColors(url: string, config: Config): Promise<Result> {
    throw new Error('not implemented');
}

async (url: string) => {
    const colors = await getColors(url, {dominant: true});
    // no error here
    let example: string = colors.dominant;
};

Playground Link

Upvotes: 2

Related Questions