Is it possible to Exclude an empty object from a union?

1.3k Views Asked by At

I have a union of two types, one of which is an empty obj.

type U = {} | { a: number } // | { b: string } | { c: boolean } ....

I would like to exclude the empty object from the union however Exclude is no help

type A = Exclude<U, {}>
// A = never

I tried using as const but it's the same result

const empty = {} as const
type Empty = typeof empty
type U = Empty | { a: number }
type A = Exclude<U, Empty>
//type A = never

The extra Irony is that excluding the other properties is straightforward

  type B = Exclude<U, { a: number }>
  // type B = {}

TS Playground

So is it possible to exclude an empty interface from other interfaces in a union?

3

There are 3 best solutions below

1
lonewarrior556 On BEST ANSWER

Answering my own question..

If you use AtLeastOne from @lukasgeiter answer here: Exclude empty object from Partial type

you can do the following:

type AtLeastOne<T, U = {[K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];
    
type ExcludeEmpty<T> = T extends AtLeastOne<T> ? T : never; 
    
type U = {} | { a: number } | { b: string }
    
type Foo = ExcludeEmpty<U> // { a: number } | { b: string }

TSplayground

2
Dipesh Dulal On

From the docs for conditional typing here, you can actually assign types based on some condition.

T extends U ? X : Y

So for the above question, what you could do is make use of keyof keyword that is used to extract keys from objects. When there is no any keys found the type is never so, we can check if the keyof object extends never or not i.e.

 keyof K extends never

So combing conditional typing below;

const empty = {} as const
type Empty = typeof empty

type NoEmpty<K> = keyof K extends never ? never : K;

type C = NoEmpty<Empty>;

type U = NoEmpty<Empty> | NoEmpty<{ a: number }>

You can finally see that type of the U to be non empty object i.e excluding empty object. Check this playground

0
Sam On

Here's a simpler way:

type WithoutEmpty<T> = T extends T ? {} extends T ? never : T : never

You need to start the expression with T to make it a "naked type parameter" otherwise it won't be distributive over unions. See this explanation.

TS playground