109

We have a structure that is like the following:

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

Which is almost the same as saying

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

Except that the Partial will allow an empty object to be passed in whereas the initial type requires one of the properties to be passed in.

How can I create a generic like Partial, but that behaves like my structure above?

Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217

7 Answers7

171

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 property
const error1: LinkRestSource = { model: 'a', titel: 'c' } // excess property on string literal
starball
  • 20,030
  • 7
  • 43
  • 238
jcalz
  • 264,269
  • 27
  • 359
  • 360
  • 9
    great work! Interesting solution. TypeScript is very powerful isn't it? – cevek Jan 15 '18 at 19:20
  • 3
    I think it should be in something like typescript hacks repository, very powerful solution – cevek Jan 15 '18 at 19:26
  • Sorry it took me forever to accept it, it's not exactly what I needed because I needed two properties to be set, but it's really powerful and probably the best we can do. – Ruan Mendes Jul 13 '18 at 13:25
  • 5
    @JuanMendes Here's `type AtLeastTwo & AtLeastOne> }> = Partial & U[keyof U]` – Dykam Aug 23 '19 at 18:31
  • 4
    And here's it for any N properties: https://gist.github.com/Dykam/2272e696435ee3e37012b065365cedf5 – Dykam Aug 23 '19 at 18:38
  • 1
    @jcalz Awesome type, very useful! I found that it doesn't enforce types that have optional parameters, but found a simple fix by splitting the implementation into two types: `type AtLeastOne = Partial & U>[keyof U]` and `type U = {[K in keyof T]: Pick}` – Enteleform May 04 '20 at 00:11
  • 1
    `Partial &` is not needed because of how TS handles unions of objects with different fields. – kirilloid Mar 11 '21 at 13:31
  • 1
    @kirilloid it depends on the use case; the question as asked seems to want to constrain the missing properties to `undefined` which is not guaranteed if you leave out `Partial` completely; see [here](https://tsplay.dev/mxoBxN). – jcalz Mar 11 '21 at 14:24
  • Now what about an `AtMostOne`? Like an either / or type? :) – fullStackChris Feb 15 '22 at 14:32
  • Just found one: https://stackoverflow.com/questions/52132696/typescript-require-one-parameter-or-the-other-but-not-neither – fullStackChris Feb 15 '22 at 14:42
  • doesn't work when one of the properties are optional. This worked when wrapping with Required utility `type AtLeastOne> = keyof T extends infer K ? K extends string ? Pick, K & keyof T> & Partial : never : never;` – Caleb Taylor May 31 '22 at 19:24
  • Great answer @jcalz!! Like a few others here though, I also found that this does not work when one or all of the properties is optional. Here's my version: `type AtLeastOne]: Pick, K> }> = Partial & U[keyof U];` The only change to your solution is that instead of picking from `T`, we pick the keys and values from `Required`. – Bart van Oort Oct 04 '22 at 20:27
  • wonderful answer. – OhadBasan Nov 20 '22 at 08:58
104

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

type 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.

type LinkRestSource = AtLeast<T, 'model' | 'rel'>
micfan
  • 800
  • 8
  • 12
aegatlin
  • 1,711
  • 1
  • 13
  • 7
  • 1
    This is pretty cool. It's really flexible, elegant and easy to understand. However, it's not what I need because if I use your example, it would require both model and rel to be passed in but passing in `{rel: "edit"}` should work. TS error: `Property 'rel' is missing in type '{ model: string; }' but required in type 'Pick<{ model: string; rel: string; title: string; }, "model" | "rel">'`. – Ruan Mendes Sep 05 '19 at 12:30
  • 1
    See https://www.typescriptlang.org/play/#code/C4TwDgpgBAgsAyECGBnYAeAKgGigaSggA9gIA7AExSgGsIQB7AMykwD4oBeKABSQCdgASyQAbLBwBkvIQGMaWXHjYBuAFBrQkKPCFkaAJQhoAygwCu-WdG5xEqDAG8AtgwoRRALihp+egOa4-B7evgG4wsCiEKHAfmT+AL64AESu7qIpUAA+UCnBmaoasgxkaFCieobGwGaW1t66+kamFlY2UC5uIXkgEMApiepAA – Ruan Mendes Sep 05 '19 at 12:30
  • Hey @JuanMendes thank you for the feedback. Yeah, this will only work if you can be explicit about which keys you want, so it's not a strict solution to your problem. I've been thinking for a bit now, and can't come up with another solution besides the one you picked above. It's clever, but a bit confusing (at least for me). – aegatlin Oct 23 '19 at 16:26
21

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 property
const error1: LinkRestSource = { model: 'a', titel: 'c' } // incorrectly spelled property

and here's the TS playground link to try it

Gabe O'Leary
  • 2,010
  • 4
  • 24
  • 41
gafi
  • 12,113
  • 2
  • 30
  • 32
  • 1
    The output of what you did will be: `type LinkRestSource = Pick | Pick | Pick` You need to do: `type AtLeastOne = Partial & { [K in keyof T]: Pick }[keyof T];` to achieve what is required. – Kunal Kumar Mar 29 '22 at 05:21
  • It's a bit counter-intuitive to write`AtLeastOne<{a: string; b: string}>` versus `AtLeastOne<{a?: string; b?: string}>`. Why would I want at least one when they are all defined in the first place? Your solution does not cover the other case. – Oleg Mar 21 '23 at 16:22
4

Unfortunately the above answers didn't work for me.
Either because the compiler couldn't catch the errors or because my IDE could not retrieve the expected attributes of an object even when it's type was annotated.

The following worked perfectly, and was taken from the official microsoft azure/keyvault-certificates package:

type RequireAtLeastOne<T> = { [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>; }[keyof T]
Teodoro
  • 1,194
  • 8
  • 22
2

Maybe something like that:

type X<A, B, C> = (A & Partial<B> & Partial<C>) | (Partial<A> & B & Partial<C>) | (Partial<A> & Partial<B> & C);
type LinkRestSource = X<{ model: string }, { rel: string }, { title: string }>
var d: LinkRestSource = {rel: 'sdf'};  

But it little bit messy :)

or

type Y<A, B, C> = Partial<A & B & C> & (A | B | C);
cevek
  • 862
  • 1
  • 7
  • 16
  • 1
    This is along the lines of where I'm trying to get to but it's not generic and would require as much code as what I typed above – Ruan Mendes Jan 12 '18 at 19:41
1

Another way and if you need keep some properties required and at least one of rest required too. See Typescript Playground example.

The base interface could looks like:

  export interface MainData {
    name: string;
    CRF: string;
    email?: string;
    cellphone?: string;
    facebookId?: string;
  }

...and if you only need at least one between 'email', 'cellphone' and 'facebookId', change and merge interfaces without optional symbol for every propoerty:

export interface registByEmail extends Omit<MainData, 'email'> { email: string }
export interface registByCellphone extends Omit<MainData, 'cellphone'> { cellphone: string }
export interface registByFacebook extends Omit<MainData, 'facebookId'> { facebookId: string }

export type RegistData = registByCellphone | registByEmail | registByFacebook

And results will looks like:

// language throws error
let client: RegistData = { name, CRF }
// its ok
let client: RegistData = { name, CRF, email }
let client: RegistData = { name, CRF, cellphone }
let client: RegistData = { name, CRF, facebookId }
let client: RegistData = { name, CRF, email, cellphone }


Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
jgu7man
  • 326
  • 1
  • 3
  • 10
0

In my case I wanted at least one property to be actually set (not just drawn from the union, in which some paths had undefined values by design).

The simplest formulation I could produce was...

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

I didn't find any of the terser approaches above to work, when dealing with a complex union like e.g. {concurrent:number} | {concurrent?:never} and the more long-winded ones looked terrifying and I'd rather understand my types fully.

My approach converged on a variant ofthe solution by gafi which was type AtLeastOne<T> = { [K in keyof T]: Pick<T, K> }[keyof T] but where crucially I pick from Required<T>, else undefined remains in the set of valid property values coming from my type unions (and it therefore still doesn't exclude the empty object).

It should be possible to use the above in isolation, but for reference my full solution for defining a non-empty ScheduleOptions type, backed by a complex union, is shown below. This example also shows a definition for AllOrNothing<T>, which may be a complementary type for these kinds of problems...

/** Allows ConcurrencyLimit properties, or IntervalLimit properties or both, but requires at least one group to be fully set */
type ScheduleOptions = SomeLimit & {
  errorHandler?: (err: unknown) => unknown;
};

/** A limit on the number of pending promises (created but not yet settled) */
interface ConcurrencyLimit {
  concurrency: number;
}

/** A limit on the number of promises created within a millisecond interval */
interface IntervalLimit {
  intervalCap: number;
  intervalMs: number;
}

/** Allow any limit to be set or unset (implicitly includes case of no limits set, which we will exclude in the next step) */
type AnyLimit = AllOrNothing<ConcurrencyLimit> & AllOrNothing<IntervalLimit>;

/** Require at least some limit to be set (excludes case of no limits) */
type SomeLimit = AnyLimit & SomePropertyFrom<AnyLimit>;

/** Require a type's properties to be either fully present, or fully absent */
type AllOrNothing<T> =
  | T
  | {
      [k in keyof Required<T>]?: never;
    };

/** Require at least one assigned property from T */
type SomePropertyFrom<T> = { [K in keyof T]: Pick<Required<T>, K> }[keyof T];
cefn
  • 2,895
  • 19
  • 28