Is it possible to have a conditional type that can test for objects that can be empty?
For example:
function test<T>(a: T): T extends {} ? string : never {
return null
}
let o1: {}
let o2: { fox? }
let o3: { fox }
test(o1)
test(o2)
test(o3) // this one should be 'never'
Since the conditional type test also for inheritance, all 3 cases yield 'string' but I want to yield 'never' if at least one property of the type is required (non empty object type like o3)
UPDATE
When I wrote this question, I tried to nail down the cause of a problem I was having. I though I should solve my doubt not my problem and also simplify my question. However the answers deviate from my problem.
Basically I was trying to build a function where the first argument is an object and the second one is optional if the first can be fully partial (initialized with {})
function test<T extends {}>(a: T, ...x: T extends {} ? [never?] : [any])
function test(a, b) {
return null
}
let o1: {}
let o2: { fox? }
let o3: { fox }
test(o1) // ok
test(o2) // ok
test(o3) // should fail and require second argument
You can do it in a relatively simple way with the keyof operator.
Remeber that never is essentially the equivalent of the empty set
type Foo = keyof {} // never
type Bar = keyof { a: 1, b: 2 } // "a" | "b"
Applied to your use case, this becomes
declare function f<T>(t: T): keyof T extends never ? string : never
const a = f({}) //string
const b = f({ a: 1 }) //never
edit
I'm not sure I fully understand the use case you've now included in the question, but if the idea is to treat an object with only optional like like an empty object, you can do it with some more type machinery.
Let's borrow the definition of OptionalPropertyOf from this question.
Given an object of type T where T extends object, we can define the following
type OptionalPropertyOf<T extends object> = Exclude<{
[K in keyof T]: T extends Record<K, T[K]>
? never
: K
}[keyof T], undefined>
type ExcludeOptionalPropertyOf<T extends object> = Omit<T, OptionalPropertyOf<T>>
type Foo = { a: number, b: string }
type OptFoo = OptionalPropertyOf<Foo> // never
type NonOptFoo = ExcludeOptionalPropertyOf<Foo> // { a: number, b: string }
type Bar = { a: number, b?: string }
type OptBar = OptionalPropertyOf<Bar> // "b"
type NonOptBar = ExcludeOptionalPropertyOf<Bar> // { a: number }
type Baz = { a?: number, b?: string }
type OptBaz = OptionalPropertyOf<Baz> // "a" | "b"
type NonOptBaz = ExcludeOptionalPropertyOf<Baz> // {}
Then, let's slightly change the definition of f to
declare function f<T extends object>(t: T): keyof ExcludeOptionalPropertyOf<T> extends never ? string : never
And now you get what you were looking for
declare const a: Foo
declare const b: Bar
declare const c: Baz
declare const d: {}
const fa = f(a) // never
const fb = f(b) // never
const fc = f(c) // string
const fd = f(d) // string
Playground link
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With