rssfrncs
rssfrncs

Reputation: 1011

Make a property required when another property is defined (discriminated union)

I want to write a type that allows for object creation with some core properties that are always required and an optional property imageUrl which when defined as a string means imageAltText is also required as a string,

{
 id: "test",
 imageUrl: "test",
 imageAltText: "test"
}

I also want it to work so that when imageUrl is not defined I do not expect imageAltText to be defined.

{
 id: "test"
}

I've defined the type as the following,

(
  {
    /** image url for the banner of the card. Will display a blue background without an image */
    imageUrl?: undefined;
  } 
| 
  {
    /** image url for the banner of the card. Will display a blue background without an image */
    imageUrl: string;
      /** alternative text for the banner image */
    imageAltText: string;
  }
)

However, by making `imageUrl` optional `?` typescript allows me to write `imageAltText` even when `imageUrl` is undefined. 

Upvotes: 5

Views: 1024

Answers (2)

Lesiak
Lesiak

Reputation: 25966

type ForbidProps<T> = {
  [P in keyof T]?: never;
}

type OptionalImagePart = {
  imageUrl: string;
  imageAltText: string;
}

type Image =   { id: number; } & (OptionalImagePart | ForbidProps<OptionalImagePart>)

const image1Ok: Image = {
  id: 1
}

const image2Ok: Image = {
  id: 1,
  imageUrl: 'imageUrl',
  imageAltText: 'imageAltText' 
}

const image3Error: Image = { // Expected error
  id: 1,
  imageUrl: 'imageUrl' 
}

const image4Error: Image = { // Expected error
  id: 1,
  imageAltText: 'imageAltText' 
}

Upvotes: 3

Here you have:


type RequiredParams<T = {}> = {
    id: string
} & T

type UnionParams =
    | RequiredParams<{ imageUrl: string; imageAltText: string; }>
    | RequiredParams

type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> =
    T extends any
    ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;

type StrictUnion<T> = StrictUnionHelper<T, T>

type Result = StrictUnion<UnionParams>

const a: Result = { id: 'sdf' }; // ok
const b: Result = { id: 'sdf', imageUrl: 'sdf', imageAltText: 'sdf' } // ok
const c: Result = { id: 'sdf', imageUrl: 'sdf' } // error
const d: Result = { id: 'sdf', imageAltText: 'sdf' } // error

All gredits goes to @Titian Cernicova-Dragomir

Upvotes: 3

Related Questions