Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript make a property optional based on value of another property

Tags:

typescript

I'm new to Typescript and I couldn't find any solutions for below scenario. Any suggestion will be greatly helpful.

I would like to make the property options optional if type is SHORT_TEXT or LONG_TEXT.

export interface Question {
  [keyName: string]: any;
  type: QuestionType;
  id: string;
  label: string;
  helpText?: string;
  placeholder?: string;
  value?: string;
  options?: Option[];
  validations?: any;
  required?: boolean;
  priority?: number;
  intent?: Intent[];
}

export type QuestionType =
  | "DROPDOWN"
  | "CHECKBOX"
  | "RADIO"
  | "SHORT_TEXT"
  | "LONG_TEXT";

export interface Option {
  id: string;
  label: string;
  value?: string;
}
like image 554
Pugazh Avatar asked Dec 29 '25 06:12

Pugazh


1 Answers

I believe what you're looking for is a "Discriminated Union". Essentially as you described it: the value of a particular property can be used to narrow the type in a union, and in this case you want one (or more) types where the options property is required, and one type where it is optional.

export type QuestionType =
  | "DROPDOWN"
  | "CHECKBOX"
  | "RADIO"
  | "SHORT_TEXT"
  | "LONG_TEXT";

export interface Option {
  id: string;
  label: string;
  value?: string;
}

export interface Intent {};

export interface BaseQuestion {
  [keyName: string]: any;
  type: QuestionType;
  id: string;
  label: string;
  helpText?: string;
  placeholder?: string;
  value?: string;
  validations?: any;
  required?: boolean;
  priority?: number;
  intent?: Intent[];
}

export interface TextQuestion extends BaseQuestion {
    type: "SHORT_TEXT" | "LONG_TEXT";
    options?: Option[];
}

export interface OptionQuestion extends BaseQuestion {
    type: "DROPDOWN" | "CHECKBOX" | "RADIO";
    options: Option[];
}

export type Question = TextQuestion | OptionQuestion;


const q: Question = {
    type: "SHORT_TEXT",
    id: "id_foo",
    label: "Test Question",
};

You can try this out in the Typescript playground: you'll see that the validator doesn't have an issue with the q object, even though it doesn't have an options property. But if you change the type property on q to be "DROPDOWN" or something other than "SHORT_TEXT" or "LONG_TEXT", the validator will raise an error.

You'll also notice that if you type q in the interactive playground, the hint will have it cast as a TextQuestion rather than a Question, since the type has been narrowed. At least in this example, that actually prevents you from changing the q.type property later, but keep in mind that guard will only in blocks where the type of that union has already been narrowed. If you're looping over a list of questions, for example, and you reassign the type:

(questions as Question[]).forEach(q => {
  q.type = "DROPDOWN";
});

Typescript hasn't narrowed each object's type in this case, and won't guard against you reassigning the type.

Hope this helps! ✌️

like image 156
Niko Chaffin Avatar answered Dec 31 '25 00:12

Niko Chaffin