1

I have the following type (simplified):

type ValueRepresents = {
    boolean: true
    number?: false
    other?: false
} |
{
    boolean?: false
    number: true
    other?: false
} |
{
    boolean?: false
    number?: false
    other: true
}

My actual type has many more possible keys. Is there a way to generate this type from a list of possible keys to make it only valid to have one key with value set to true? Something like:

type ValueTypes = "boolean" | "number" | "other"
type ValueRepresents <T extends ValueTypes> = {
    [k in ValueTypes]: k extends T ? true : false
}
const a: ValueRepresents<"boolean"> = {
    boolean: true,
    number: false,
    other: false,
}

But I'm aiming for being able to use:

// should pass
const a: ValueRepresents = { boolean: true }

// should pass
const a2: ValueRepresents = {
    boolean: true,
    number: false,
}

// should error
const a3: ValueRepresents = {
    boolean: true,
    number: true,
}

// should error
const a4: ValueRepresents = {}

I also tried following this answer but was not yet successful with:

type ValueRepresents <T extends ValueTypes> = {
    [k in Exclude<T, ValueTypes>]?: false
} & { [k in T]: true }
AJP
  • 26,547
  • 23
  • 88
  • 127

1 Answers1

4

You can try creating an union type like this

type ValueTypes = "boolean" | "number" | "other"

type ValueRepresents = ({
    [K in ValueTypes]: Partial<Record<Exclude<ValueTypes, K>, false>> & Record<K, true>
})[ValueTypes]

TypeScript Playground


TL;DR

I don't think this expression has a specific name. I've seen similar examples used in the docs in Advanced Types, but I'll try to explain how it works.

type ValueRepresents = {
    [K in ValueTypes]: Partial<Record<Exclude<ValueTypes, K>, false>> & Record<K, true>
}

creates type equivalent to:

type ValueRepresents = {
  boolean: {
    boolean: true;
    number?: false;
    other?: false;
  };
  number: {
    boolean?: false;
    number: true;
    other?: false;
  };
  other: {
    boolean?: false;
    number?: true;
    other: true;
  };
};

and by adding the union type in square brackets [ValueTypes] it extracts the values of those (all) keys in another union type equivalent to:

type ValueRepresents =
  | {
      boolean: true;
      number?: false;
      other?: false;
    }
  | {
      boolean?: false;
      number: true;
      other?: false;
    }
  | {
      boolean?: false;
      number?: true;
      other: true;
    };
Teneff
  • 30,564
  • 13
  • 72
  • 103
  • This is brilliant. Thank you. I've never seen this syntax before `)[ValueTypes]`, could you point me to some docs explaining it please? I played around with it now so I have a partial feel for what it's doing but I don't know what it's called. – AJP Jun 28 '21 at 21:11
  • 1
    @AJP I've added short explanation on how it works – Teneff Jun 29 '21 at 14:05