Home > Enterprise >  Make all optional keys of a specific set be required if one is provided
Make all optional keys of a specific set be required if one is provided

Time:02-04

I want to create a utility type in TS that should have two generic type arguments. It should make all the keys required for the first generic type argument. The keys for the second one should be optional but be all required if at least one of them is set.

type RequiredAndAllOrNothing<R, S> = { /* How? */ }
// example
type Example = RequiredAndAllOrNothing<
  { aaaa: number; bbbb: boolean; cccc: boolean},
  { dddd: number; eeee: boolean; ffff: boolean }
>
// should be ok:
const x: Example = { aaaa: 1, bbbb: true, cccc: false }
const y: Example = { aaaa: 1, bbbb: true, cccc: false, dddd: 1, eeee: true, ffff: false, }
// should not be ok, dddd was provided but neither eeee nor ffff
const z: Example = { aaaa: 1, bbbb: true, cccc: false, dddd: 1, }

I have tried this naive approach, but it doesn't work

// Same result as just Required<R & S>
type RequiredAndAllOrNothing<R, S> = Required<R> & (Required<S> | Record<string, never>)

although this part does what it should:

// accept object if it has all the properties or if it is empty
type AllOrNothing<T> = Required<T> | Record<string, never>

What am I missing?

CodePudding user response:

type EnsureKeysAreMissing<T> = { [K in keyof T]?: never }

The EnsureKeysAreMissing type here creates an object type where each key must be missing. This is the "nothing" case.

And the "all" case is just the input.

So let's use that:

type RequiredAndAllOrNothing<R, S> = R & (EnsureKeysAreMissing<S> | S)

Since R is the required part, we can just use that without modification.

Then we intersect & that with a union of two possible types.

One member is EnsureKeysAreMissing<S> a described above.

The other member of that union is just S, and we can use that without modification.

// example
type Example = RequiredAndAllOrNothing<
  { a: number; b: boolean; c: boolean },
  { d: number; e: boolean; f: boolean }
>

// should be ok:
const x: Example = { a: 1, b: true, c: false }
const y: Example = { a: 1, b: true, c: false, d: 1, e: true, f: false, }

// should not be ok, d was provided but neither e nor f
const z: Example = { a: 1, b: true, c: false, d: 1, }
/*
Type '{ a: number; b: true; c: false; d: number; }' is not assignable to type 'Example'.
  Type '{ a: number; b: true; c: false; d: number; }' is not assignable to type '{ a: number; b: boolean; c: boolean; } & { d: number; e: boolean; f: boolean; }'.
    Type '{ a: number; b: true; c: false; d: number; }' is missing the following properties from type '{ d: number; e: boolean; f: boolean; }': e, f(2322)
*/

See Playground

  • Related