type A = {
foo: string;
bar: number;
baz: boolean;
}
type AtLeastOne<Obj, Keys = keyof Obj> = Keys extends keyof Obj ? Pick<Obj, Keys> : never
type NonEmpty<T> = Partial<T> & AtLeastOne<T>
// Partial<A> & (Pick<A, "foo"> | Pick<A, "bar"> | Pick<A, "baz">)
type Result = NonEmpty<A>
//compiles
const b1: Result = {
foo: "yeah"
}
//errors
const b2: Result = {}
const b3: Result = { lala: "lala" }
const b4: Result = { foo: "foo is alowed", but_not_this: false }
Playground
Explanation
Partial<A> & Pick<A, "foo"> | Pick<A, "bar"> | Pick<A, "baz">
this is minimum required type you should end up.
First of all we need to make sure that object is not empty. It should have either of three props.
Consider distributive-conditional-types
type AtLeastOne<Obj, Keys = keyof Obj> = Keys extends keyof Obj ? Pick<Obj, Keys> : never
According to the docs, Pick<Obj, Keys>
- will be applied to each key. Hence, AtLeastOne
returns Pick<A, "foo"> | Pick<A, "bar"> | Pick<A, "baz">
.
Now the easiest part, you need just use intersection in order to merge return type of AtLeastOne
and Partial<A>
type NonEmpty<T> = Partial<T> & AtLeastOne<T>
More interesting examples you will find in my typescript blog