How to check whether all fields in an object are 'false' while only one is 'true'?

748 Views Asked by At

I have an object with many boolean fields like so:

type SomeObject = {
  A: boolean
  B: boolean
  C: boolean
  ....
}

Is there an easy/efficient way to check if the OR of all other fields (besides a specified field) are false?

We can do it the brute-force way by just checking each field manually:

let foo:SomeObject = {
  A: false
  B: false
  C: false
}
let isA = !(foo.B || foo.C)
let isB = !(foo.A || foo.C)
let isC = !(foo.A || foo.B)

But I'm sure there is a more elegant way of accomplishing this.

1

There are 1 best solutions below

0
On BEST ANSWER

If you only need compile-time checking, it is very easy to do with a mapped type and an intersection (an identity mapped type thrown in for "prettifying" the generated type):

type SomeObject = {
  A: boolean
  B: boolean
  C: boolean
}

type OnlyOneTrue<T extends object, K extends keyof T> = {
    [P in K] : true
} & {
    [P in Exclude<keyof T, K>]: false
};

type Identity<T> = { [P in keyof T] : T[P] }

//{ A: true; } & { B: false; C: false; }
type test = OnlyOneTrue<SomeObject, "A">;

//{ A: true; B: false; C: false; }
type pretty = Identity<test>;

If you need compile-time type guarding so as the type can be narrowed later, then you need to use a union. The previous type can become a stepping stone for another utility type:

type PossibleOnlyTrue<T extends object> = {
    [P in keyof T]: OnlyOneTrue<T, P>
}[keyof T];

//OnlyOneTrue<SomeObject, "A"> | OnlyOneTrue<SomeObject, "B"> | OnlyOneTrue<SomeObject, "C">
type union = PossibleOnlyTrue<SomeObject>;

const process = (obj: union) => {
    //type is narrowed to a union member:
    if(obj.A) {
        console.log(obj); //OnlyOneTrue<SomeObject, "A">
    }
};

If you need runtime checks on top, there are options. Below is both a type and runtime guard based on the previous helper mapped types (PossibleOnlyTrue and OnlyOneTrue):

const runtime = <K extends keyof union>(obj: union, key: K) : obj is Extract<union, { [P in K] : true }> => {
    return Object.entries(obj).every(([k,v]) => k === key || !v );
};

{
    const obj:union = { A:true, B:false,C:false };
    if(runtime(obj, "A")) obj //OK, obj is OnlyOneTrue<SomeObject, "A">
}

Playground