How to create a Partial-like that requires a single property to be set How to create a Partial-like that requires a single property to be set typescript typescript

How to create a Partial-like that requires a single property to be set


I think I have a solution for you. You're looking for something that takes a type T and produces a related type which contains at least one property from T. That is, it's like Partial<T> but excludes the empty object.

If so, here it is:

type AtLeastOne<T, U = {[K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]

To dissect it: first of all, AtLeastOne<T> is Partial<T> intersected with something. U[keyof U] means that it's the union of all property values of U. And I've defined (the default value of) U to be a mapped type where each property of T is mapped to Pick<T, K>, a single-property type for the key K. (For example, Pick<{foo: string, bar: number},'foo'> is equivalent to {foo: string}... it "picks" the 'foo' property from the original type.) Meaning that U[keyof U] in this case is the union of all possible single-property types from T.

Hmm, that might be confusing. Let's see step-by-step how it operates on the following concrete type:

type FullLinkRestSource = {  model: string;  rel: string;  title: string;}type LinkRestSource = AtLeastOne<FullLinkRestSource>

That expands to

type LinkRestSource = AtLeastOne<FullLinkRestSource, {  [K in keyof FullLinkRestSource]: Pick<FullLinkRestSource, K>}>

or

type LinkRestSource = AtLeastOne<FullLinkRestSource, {  model: Pick<FullLinkRestSource, 'model'>,  rel: Pick<FullLinkRestSource, 'rel'>,  title: Pick<FullLinkRestSource, 'title'>}>

or

type LinkRestSource = AtLeastOne<FullLinkRestSource, {  model: {model: string},  rel: {rel: string},  title: {title: string}>}>

or

type LinkRestSource = Partial<FullLinkRestSource> & {  model: {model: string},  rel: {rel: string},  title: {title: string}>}[keyof {  model: {model: string},  rel: {rel: string},  title: {title: string}>}]

or

type LinkRestSource = Partial<FullLinkRestSource> & {  model: {model: string},  rel: {rel: string},  title: {title: string}>}['model' | 'rel' | 'title']

or

type LinkRestSource = Partial<FullLinkRestSource> &  ({model: string} | {rel: string} | {title: string})

or

type LinkRestSource = {model?: string, rel?: string, title?: string} &   ({model: string} | {rel: string} | {title: string})

or

type LinkRestSource = { model: string, rel?: string, title?: string }   | {model?: string, rel: string, title?: string}   | {model?: string, rel?: string, title: string}

which is, I think, what you want.

You can test it out:

const okay0: LinkRestSource = { model: 'a', rel: 'b', title: 'c' }const okay1: LinkRestSource = { model: 'a', rel: 'b' }const okay2: LinkRestSource = { model: 'a' }const okay3: LinkRestSource = { rel: 'b' }const okay4: LinkRestSource = { title: 'c' }const error0: LinkRestSource = {} // missing propertyconst error1: LinkRestSource = { model: 'a', titel: 'c' } // excess property on string literal

So, does that work for you? Good luck!


There's another solution if you know which properties you want.

AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>

This would also allow you to lock in multiple keys of a type, e.g. AtLeast<T, 'model' | 'rel'>.


A simpler version of the solution by jcalz:

type AtLeastOne<T> = { [K in keyof T]: Pick<T, K> }[keyof T]

so the whole implementation becomes

type FullLinkRestSource = {  model: string;  rel: string;  title: string;}type AtLeastOne<T> = { [K in keyof T]: Pick<T, K> }[keyof T]type LinkRestSource = AtLeastOne<FullLinkRestSource>const okay0: LinkRestSource = { model: 'a', rel: 'b', title: 'c' }const okay1: LinkRestSource = { model: 'a', rel: 'b' }const okay2: LinkRestSource = { model: 'a' }const okay3: LinkRestSource = { rel: 'b' }const okay4: LinkRestSource = { title: 'c' }const error0: LinkRestSource = {} // missing propertyconst error1: LinkRestSource = { model: 'a', titel: 'c' } // excess property on string literal

and here's the TS playground link to try it