Is it possible to retrieve all mandatory attributes of a TypeScript object?

Is there a method or approach available that can retrieve all necessary properties from a TypeScript interface or an object? For instance, something along the lines of

Object.getOwnPropertyDescriptors(myObject)
or keyof T, but with the specific details on whether a property is required or optional.

Answer №1

During runtime, it is impossible to determine the required or optional properties of an object in TypeScript as this information is erased by the time the code executes. Although you can include your own runtime details using decorators, modifying the actual code that generates classes and objects is necessary. Therefore, retrieving a list of required property names from an object or constructor at runtime is unattainable.


However, at design time, it is feasible to extract the mandatory and optional keys of a type as a subtype of keyof T. This approach utilizes conditional types and leverages the fact that an empty object type {} is assignable to a weak type (a type with no obligatory properties). For example:

type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];
type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T];

Here is an illustration of how to use it:

interface SomeType {
  required: string;
  optional?: number;
  requiredButPossiblyUndefined: boolean | undefined;
}

type SomeTypeRequiredKeys = RequiredKeys<SomeType>; 
// type SomeTypeRequiredKeys = "required" | "requiredButPossiblyUndefined" 🙂

type SomeTypeOptionalKeys = OptionalKeys<SomeType>; 
// type SomeTypeOptionalKeys = "optional" 🙂

Note that this method may not work effectively with types containing index signatures:

interface SomeType {
  required: string;
  optional?: number;
  requiredButPossiblyUndefined: boolean | undefined;
  [k: string]: unknown; // index signature
} 

type SomeTypeRequiredKeys = RequiredKeys<SomeType>;
// type SomeTypeRequiredKeys = never 🙁

type SomeTypeOptionalKeys = OptionalKeys<SomeType>;
// type SomeTypeOptionalKeys = string 🙁

If you require handling indexable types, a more intricate solution involves extracting known literal keys first and then identifying the required and optional properties:

(EDIT: The following was updated to address changes in TS4.3, see ms/TS#44143)

type RequiredLiteralKeys<T> = keyof { [K in keyof T as string extends K ? never : number extends K ? never :
    {} extends Pick<T, K> ? never : K]: 0 }

type OptionalLiteralKeys<T> = keyof { [K in keyof T as string extends K ? never : number extends K ? never :
    {} extends Pick<T, K> ? K : never]: 0 }

type IndexKeys<T> = string extends keyof T ? string : number extends keyof T ? number : never;

This results in:

type SomeTypeRequiredKeys = RequiredLiteralKeys<SomeType>;
// type SomeTypeRequiredKeys = "required" | "requiredButPossiblyUndefined" 🙂

type SomeTypeOptionalKeys = OptionalLiteralKeys<SomeType>; 
// type SomeTypeOptionalKeys = "optional" 🙂

type SomeTypeIndexKeys = IndexKeys<SomeType>;
// type SomeTypeIndexKeys = string 🙂

Answer №3

Below is the proposed solution:

type _OptionalKeys<A extends object, B extends object> = {
  [K in KnownKeys<A> & KnownKeys<B>]: Pick<A, K> extends Pick<B, K> ? never : K
};

/**
 * The function OptionalKeys retrieves optional keys from a type `T`.
 * For example, `{ a: string; b: string | undefined; c?: string }` => `'c'`.
 */
export type OptionalKeys<T extends object> = _OptionalKeys<
  T,
  Required<T>
>[KnownKeys<T>];

In essence, we are identifying OptionalKeys to later extract the RequiredKeys.

/**
 * RequiredKeys retrieves required keys from a type `T`.
 * For example, `{ a: string; b: string | undefined; c?: string }` => `'b' | 'c'`.
 */
export type RequiredKeys<T extends object> = Exclude<
  KnownKeys<T>,
  OptionalKeys<T>
>;

Now, what exactly does KnownKeys signify?

/**
 * Extracts the keys of a union type
 */
// tslint:disable-next-line:no-any
export type KeysOfUnion<T> = T extends any ? keyof T : never;

/**
 * Retrieves known keys from an object – irrespective of having an index signature.
 */
export type KnownKeys<T extends object> = {
  [K in keyof T]: string extends K ? never : number extends K ? never : K
} extends { [_ in keyof T]: infer U }
  ? {} extends U
    ? never
    : U
  : never;

Additionally, you could reverse the process by initially identifying RequiredKeys, then determining OptionalKeys, simply by altering

Pick<A, K> extends Pick<B, K>
to
Pick<B, K> extends Pick<A, K>
within _OptionalKeys and renaming it to _RequiredKeys.

This solution (originally created for my rbx package) can be applied on both union types and singleton types with ease.

Answer №4

This method proved to be effective for me

type MandatoryProps<T> = {
    [K in keyof T as string extends K ? never : number extends K ? never : {} extends Pick<T, K> ? never : K]: T[K]
  }

interface Person {
    id: string;
    name: string;
    age?: string;
    height?: number;
    hobbies: string[];
}

type Result = MandatoryProps<Person>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Generating images with HTML canvas only occurs once before stopping

I successfully implemented an image generation button using Nextjs and the HTML canvas element. The functionality works almost flawlessly - when a user clicks the "Generate Image" button, it creates an image containing smaller images with labels underneath ...

Gain insights on Stripe Webhooks with Firebase Functions and Node.js

I've been struggling to integrate Firebase functions with Stripe webhooks for listening to events. Take a look at my code: exports.stripeEvents = functions.https.onRequest((request, response) => { try { const stripeSignature = request. ...

Trustpilot: The function window.Trustpilot.loadFromElement does not exist in Safari

I am looking to integrate 3 TrustPilots into my Angular application. import { Component, Input, OnInit } from '@angular/core'; declare global { interface Window { Trustpilot: any; } } window.Trustpilot = window.Trustpilot || {}; @Component ...

Next.js Version 13 - Unable to find solution for 'supports-color' conflict

Currently in the midst of developing a Next.js 13 app (with TypeScript) and utilizing the Sendgrid npm package. An ongoing issue keeps popping up: Module not found: Can't resolve 'supports-color' in '.../node_modules/debug/src' ...

The use of findDOMNode has been marked as outdated in StrictMode. Specifically, findDOMNode was utilized with an instance of Transition (generated by MUI Backdrop) that is contained

I encountered the following alert: Alert: detectDOMNode is now outdated in StrictMode. detectDOMNode was given an instance of Transition which resides within StrictMode. Instead, attach a ref directly to the element you wish to reference. Get more inform ...

The ng-model used within an *ngFor loop is not displaying the correct value

I am working with a JSON file in my Angular2 App. The specific JSON object I am referring to is named tempPromotion and I am trying to access the data within ['results'] like this: tempPromotion['response_payload']['ruleset_list ...

Exploring the functionality of angular reactive forms in creating intricate JSON structures

After numerous attempts to resolve the issue on my own, I am reaching out to an Angular developer for assistance. My goal is to display a JSON object in the UI: Here is the JSON Object : items={"departure":"New York","arrival":"California","stations":[ ...

Subscription Code Incrementally Triggering Upon Each Component Load

Within the initialization of my component, I have the following code: public Subscription: Subscription; ngOnInit() { this.subscription = this.myService.currentData.subscribe( dataReceived => { this.data = dataReceived; this.useDa ...

What is the best way to add items to arrays with matching titles?

I am currently working on a form that allows for the creation of duplicate sections. After submitting the form, it generates one large object. To better organize the data and make it compatible with my API, I am developing a filter function to group the du ...

Sidenav Angular Material cdkScrollable is an effective tool for creating scrollable

In Angular Material CDK, there is a special Directive called CdkScrollable that allows you to monitor ScrollEvents within a specific container. I am currently attempting to retrieve the CdkScrollable associated with the default MatSidenavContent. Unfor ...

What is the best way to transform an Observable array containing objects into an Observable that emits the data contained within those objects?

Encountering an error: Error: Type 'Observable<Country[]>' is not assignable to type 'Observable'. Type 'Country[]' is missing properties like name, tld, alpha2Code, alpha3Code and more.ts(2322 The issue might be due ...

Generating a random number to be input into the angular 2 form group index can be done by following these

One interesting feature of my form is the dynamic input field where users can easily add more fields by simply clicking on a button. These input fields are then linked to the template using ngFor, as shown below: *ngFor="let data of getTasks(myFormdata); ...

What is the correct way to utilize Global Variables in programming?

Having trouble incrementing the current page in my pagination script to call the next page via AJAX... In my TypeScript file, I declare a global variable like this; declare var getCurrentPage: number; Later in the same file, I set the value for getCurren ...

Uh-oh! Angular 6 has encountered an unexpected error with template parsing

I am currently working on an Angular application where I have integrated the FormsModule from '@angular/forms' in my app.module.ts. However, despite this, I keep encountering the error message No provider for ControlContainer. Error log: Uncaug ...

How should JSON files stored on the server side be properly utilized in Next.js?

Currently, I have a collection of JSON files that I expose through an API endpoint on my web application for global access. This allows different parts of the application to retrieve the data by sending a fetch request to itself... However, since this inf ...

Filtering an array dynamically in Typescript depending on the entered value

My task involves filtering arrays of objects based on input field values. Data data: [{ taskname: 'Test1', taskId: '1', status: 'Submitted' }, { taskname: 'Test2', taskId: '2', status: 'Re ...

Running pre-commit eslint autofix but encountering errors that do not exist

Encountering an issue with committing changes to a file due to a failed pre-commit eslint --fix task, displaying the following errors: × eslint --fix: C:\Users\user\source\repos\project\project-frontend\src\compone ...

Implementing Angular 2 - Steps to ensure a service is accessible within the app module

I'm running into an issue trying to utilize a function within a service that I believed was globally accessible. The service in question is named SavedNotificationService: import { Injectable } from '@angular/core'; @Injectable() export cl ...

Retrieving specific properties from a JSON object and logging them

I am attempting to access JSON Object properties directly and log them using the following function : loadProcesses(filter?){ this._postService.getAllProcess(filter) .subscribe( res=> { this.processListe = res; // console.log(this.p ...

Creating adaptable Object Properties using Zod

Just dipping my toes into Typescript, Zod, and Trpc. Let's say I have a schema for animals and plants. I want to keep all their shared properties in the main part of the schema, while putting more specific details into a sub-object named custom. (jus ...