TypeScript Require One of Two Properties

2024-10-07

Understanding TypeScript Interfaces

  • They help enforce type safety and code consistency.
  • They specify the properties and their types that an object must have.
  • Interfaces define the contract or shape of an object.

Requiring One of Two Properties

  • To achieve this, you can use a combination of optional properties and conditional types.

Steps

  1. Define Optional Properties

    • Create an interface with the properties you want to make optional.
    • Use a question mark (?) after the property name to indicate that it's optional.
    interface MyInterface {
        propertyA?: string;
        propertyB?: number;
    }
    
  2. Create a Conditional Type

    • Define a conditional type that checks if either propertyA or propertyB exists.
    • Use a type guard to check for the presence of each property.
    type RequireOneOf<T, K extends keyof T> = T extends Pick<T, K> ? T : never;
    
  3. Apply the Conditional Type

    • Use the conditional type to create a new interface that requires one of the optional properties.
    interface MyRequiredInterface extends RequireOneOf<MyInterface, 'propertyA' | 'propertyB'> {}
    

Explanation

  • Otherwise, the conditional type returns never, indicating that the interface doesn't meet the requirement.
  • If T extends Pick<T, K>, it means at least one of the properties in K is present, and the conditional type returns T.
  • The conditional type checks if the interface T extends Pick<T, K>.
    • Pick<T, K> extracts only the properties specified in K from T.
  • RequireOneOf<T, K extends keyof T> takes two parameters:
    • T: The interface you want to enforce the requirement on.
    • K: A union of the property keys you want to make mandatory.

Example Usage

const obj1: MyRequiredInterface = { propertyA: 'value' }; // Valid
const obj2: MyRequiredInterface = { propertyB: 123 }; // Valid
const obj3: MyRequiredInterface = {}; // Invalid, both properties are missing



interface MyInterface {
    propertyA?: string;
    propertyB?: number;
}

type RequireOneOf<T, K extends keyof T> = T extends Pick<T, K> ? T : never;

interface MyRequiredInterface extends RequireOneOf<MyInterface, 'propertyA' | 'propertyB'> {}

Breakdown

    • The conditional type uses Pick<T, K> to extract only the properties specified in K from T.
    • The MyRequiredInterface interface extends RequireOneOf<MyInterface, 'propertyA' | 'propertyB'>.
    • This ensures that any object conforming to MyRequiredInterface must have either propertyA or propertyB (or both) defined.
const obj1: MyRequiredInterface = { propertyA: 'value' }; // Valid
const obj2: MyRequiredInterface = { propertyB: 123 }; // Valid
const obj3: MyRequiredInterface = {}; // Invalid, both properties are missing
  • obj3 is invalid because it doesn't have either propertyA or propertyB.
  • obj1 and obj2 are valid because they have at least one of the required properties.

Key Points

  • The RequireOneOf type is a reusable utility that can be applied to various scenarios where you need to require one or more properties in an interface.
  • By using this approach, you can enforce strict type checks and ensure that objects of a specific interface adhere to the specified property requirements.



Alternative Methods for Requiring One of Two Properties in TypeScript Interfaces

While the conditional type approach is a common and effective method, there are a few other alternatives you can consider:

Intersection Types

  • Example
  • Concept
    Create an intersection type that combines two interfaces, each with one of the required properties.
interface PropertyAInterface {
    propertyA: string;
}

interface PropertyBInterface {
    propertyB: number;
}

type MyRequiredInterface = PropertyAInterface & PropertyBInterface;

Discriminant Unions

  • Concept
    Use a discriminant property to differentiate between objects of different types within a union.
type PropertyType = 'propertyA' | 'propertyB';

interface PropertyAInterface {
    type: PropertyType;
    propertyA: string;
}

interface PropertyBInterface {
    type: PropertyType;
    propertyB: number;
}

type MyRequiredInterface = PropertyAInterface | PropertyBInterface;

Custom Type Guards

  • Concept
    Create custom type guards to check for the presence of specific properties.
function isPropertyAInterface(obj: any): obj is PropertyAInterface {
    return 'propertyA' in obj;
}

function isPropertyBInterface(obj: any): obj is PropertyBInterface {
    return 'propertyB' in obj;
}

Optional Chaining and Nullish Coalescing

  • Concept
    Use optional chaining (?.) to safely access properties and nullish coalescing (??) to provide default values.
interface MyInterface {
    propertyA?: string;
    propertyB?: number;
}

function requireOneOf(obj: MyInterface): string | number {
    return obj.propertyA ?? obj.propertyB;
}

Choosing the Right Method

  • Optional chaining and nullish coalescing
    Can be used for more complex scenarios, but might require additional logic.
  • Custom type guards
    Provide fine-grained control over type checking.
  • Discriminant unions
    Are useful when you need to differentiate between objects based on a common property.
  • Intersection types
    Work well when you have clear-cut definitions for each property type.
  • Conditional types
    Are generally the most flexible and concise approach.

typescript



TypeScript Getters and Setters Explained

Getters and SettersIn TypeScript, getters and setters are special methods used to access or modify the values of class properties...


TypeScript: Integer Class Property

Here's an example:In this code, the age property is declared as number, indicating that it can only store integer values...


Importing TypeScript Files

Understanding Import StatementsImport statements are used to bring code from other TypeScript files into your current file...


TypeScript Value Property Error

Breakdown"The property 'value' does not exist on value of type 'HTMLElement'" This error indicates that you're trying to access the value property on an object that is of type HTMLElement...


TypeScript Callback Type Explained

Understanding Callback TypesIn TypeScript, a callback type is a type that defines the expected signature of a function that will be passed as an argument to another function...



typescript

Constructor Overloading in TypeScript

Constructor OverloadingIn TypeScript, constructor overloading allows you to define multiple constructors for a class, each with different parameter types and signatures


Set New Window Property TypeScript

Direct AssignmentThe most straightforward method is to directly assign a value to the new property:This approach creates a new property named myNewProperty on the window object and assigns the string "Hello


Dynamically Assigning Properties in TypeScript

Understanding the ConceptDynamic property assignment allows you to create or modify object properties at runtime, rather than defining them statically


TypeScript Object Literal Types

Type Definitions in Object LiteralsIn TypeScript, object literals can be annotated with type definitions to provide more precise and informative code


Class Type Checking in TypeScript

Class Type Checking in TypeScriptIn TypeScript, class type checking ensures that objects adhere to the defined structure of a class