3

please forgive me the slightly longish title.

given the following type

type A = {
    foo: string;
    bar: number;
    baz: boolean;
}

I like to create a new "partial" type B

type B = Partial<A>

such that B must contain at least one of the properties of A and only properties of A are allowed

//compiles
const b1 = {
   foo: "yeah"
}

//errors
const b2 = {}
const b3 = {lala: "lala"}
const b4 = {foo: "foo is alowed", but_not_this: false}
robkuz
  • 9,488
  • 5
  • 29
  • 50

1 Answers1

6
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