interface Person {
name: string;
age: number;
isActive: boolean;
}
type AtLeastOneKey<Obj, Keys = keyof Obj> = Keys extends keyof Obj ? Pick<Obj, Keys> : never
type NonEmptyObject<T> = Partial<T> & AtLeastOneKey<T>
// Partial<Person> & (Pick<Person, "name"> | Pick<Person, "age"> | Pick<Person, "isActive">)
type ResultantType = NonEmptyObject<Person>
//compiles
const person1: ResultantType = {
name: "John",
age: 30,
isActive: true
}
//errors
const person2: ResultantType = {}
const person3: ResultantType = { hobby: "swimming" }
const person4: ResultantType = { name: "Jane", isWorking: false }
Playground
Explanation
Partial<Person> & Pick<Person, "name"> | Pick<Person, "age"> | Pick<Person, "isActive">
this is the minimum required type that should be returned.
Firstly, we ensure that the object is not empty and has at least one of three properties defined.
Refer to distributive conditional types
type AtLeastOneKey<Obj, Keys = keyof Obj> = Keys extends keyof Obj ? Pick<Obj, Keys> : never
As per the documentation, Pick<Obj, Keys>
- will be applied to each key. Therefore, AtLeastOneKey
returns
Pick<Person, "name"> | Pick<Person, "age"> | Pick<Person, "isActive">
.
Finally, a simple intersection is used to merge the return type of AtLeastOneKey
with Partial<Person>
type NonEmptyObject<T> = Partial<T> & AtLeastOneKey<T>
For more intriguing examples, you can explore my TypeScript blog