RQman
RQman

Reputation: 469

Typescript: type with one requiered property from list of optional

I have interface with list of optional properties.

export interface OptionalIds {
  entityA_Id?: number;
  entityB_Id?: number;
  entityC_Id?: number;
}

And I have a requirement that EXACTLY one of them MUST be defined. Something like that:

export interface RequiredBId {
  entityA_Id?: undefined;
  entityB_Id: number;
  entityC_Id?: undefined;
}

export interface RequiredCId {
  entityA_Id?: undefined;
  entityB_Id?: undefined;
  entityC_Id: number;
}

export interface OptionalIds {
  entityA_Id?: number;
  entityB_Id?: number;
  entityC_Id?: number;
}

export type RestrictedOptionalIds = OptionalIds & (RequiredAId | RequiredBId | RequiredCId)

The question is: Is there other way to achieve described behaviour without weird constructions?

Upvotes: 7

Views: 824

Answers (1)

Robert Rendell
Robert Rendell

Reputation: 2289

Updated Answer

Thanks to this post for RequireOnlyOne: https://stackoverflow.com/a/49725198/4529555

type RequireOnlyOne<T, Keys extends keyof T = keyof T> =
    Pick<T, Exclude<keyof T, Keys>>
    & {
        [K in Keys]-?:
            Required<Pick<T, K>>
            & Partial<Record<Exclude<Keys, K>, undefined>>
    }[Keys]

export interface OptionalIds {
  entityA_Id?: number;
  entityB_Id?: number;
  entityC_Id?: number;
}

const exampleA: RequireOnlyOne<OptionalIds> = {
  entityA_Id: 1
}
const exampleB: RequireOnlyOne<OptionalIds> = {
  entityB_Id: 1
}

const exampleC: RequireOnlyOne<OptionalIds> = {
  entityC_Id: 1
}

// Error
const exampleMultiple: RequireOnlyOne<OptionalIds> = {
  entityA_Id: 1,
  entityB_Id: 2,
}

// Error: {} not assignable to RequireAtLeastOne<OptionalIds, keyof OptionalIds>
const exampleTsError: RequireOnlyOne<OptionalIds> = {

}

Original Answer

Thanks to this post for RequireAtLeastOne: https://stackoverflow.com/a/49725198/4529555

type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
    Pick<T, Exclude<keyof T, Keys>> 
    & {
        [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
    }[Keys]

export interface OptionalIds {
  entityA_Id?: number;
  entityB_Id?: number;
  entityC_Id?: number;
}

const exampleA: RequireAtLeastOne<OptionalIds> = {
  entityA_Id: 1
}
const exampleB: RequireAtLeastOne<OptionalIds> = {
  entityB_Id: 1
}

const exampleC: RequireAtLeastOne<OptionalIds> = {
  entityC_Id: 1
}

const exampleMultiple: RequireAtLeastOne<OptionalIds> = {
  entityA_Id: 1,
  entityB_Id: 2
}

// Error: {} not assignable to RequireAtLeastOne<OptionalIds, keyof OptionalIds>
const exampleTsError: RequireAtLeastOne<OptionalIds> = {

}

Upvotes: 2

Related Questions