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

Exploring Vue 3.3: Understanding Generics and Dynamic Properties

I'm currently diving into the generics feature in vue 3.3 and I've been pondering about defining the type of an incoming prop based on another prop value. This is my current component structure: export interface OptionProps { id: string | numb ...

Tips for preserving @typedef during the TypeScript to JavaScript transpilation process

I have a block of TypeScript code as shown below: /** * @typedef Foo * @type {Object} * @property {string} id */ type Foo = { id: string } /** * bar * @returns {Foo} */ function bar(): Foo { const foo:Foo = {id: 'foo'} return f ...

updating rows in a table

Currently, I have a grid array filled with default data retrieved from the database. This data is then displayed on the front end in a table/grid format allowing users to add and delete rows. When a row is added, I only want to insert an empty object. The ...

trouble with file paths in deno

I was attempting to use prefixes for my imports like in the example below: "paths": { "~/*": ["../../libs/*"], "@/*": ["./*"] } However, I keep encountering an error message say ...

Using a form template to bind radio buttons and automatically populate fields based on the selected radio button

My form has three radio buttons. The first one is selected by default. The second one should display an input field conditionally upon clicking it, and when the third option is selected, it should populate that input field with a certain value. div> ...

Generating an instance of a class by using the class name as a string

Before jumping to conclusions, please take a moment to read the following: In addition to TypeScript, my issue also involves Angular2. Main Goal I am in need of a method in app.component.ts that can take a string (Class Name) and generate an instance of ...

Error: Exceeded Maximum Re-Renders. React has set a limit on the number of renders to avoid infinite loops. Issue found in the Toggle Component of Next.js

I am struggling with setting a component to only display when the user wants to edit the content of an entry, and encountering an error mentioned in the title. To achieve this, I have utilized setState to manage a boolean using toggle and setToggle, then ...

Does the routing in Angular 2 get disrupted by parameter breaks in sub-modules?

In my Angular 2 application, I am encountering an issue with routing to my line module. Currently, I have two submodules - login and line. The routing to the login submodule is working well. However, when I attempt to route to the line module with route pa ...

Dynamic importing fails to locate file without .js extension

I created a small TS app recently. Inside the project, there is a file named en.js with the following content: export default { name: "test" } However, when I attempt to import it, the import does not work as expected: await import("./e ...

Tips for utilizing an object key containing a dash ("-") within it

Here is an example of the object structure: { approved_for_syndication: 1 caption: "" copyright: "" media-metadata: (3) [{…}, {…}, {…}] subtype: "photo" } How can I properly a ...

Angular2 - adding the authentication token to request headers

Within my Angular 2 application, I am faced with the task of authenticating every request by including a token in the header. A service has been set up to handle the creation of request headers and insertion of the token. The dilemma arises from the fact t ...

Discovering the ReturnType in Typescript when applied to functions within functions

Exploring the use of ReturnType to create a type based on return types of object's functions. Take a look at this example object: const sampleObject = { firstFunction: (): number => 1, secondFunction: (): string => 'a', }; The e ...

React modal not closing when clicking outside the modal in Bootstrap

I recently utilized a react-bootstrap modal to display notifications in my React project. While the modal functions correctly, I encountered an issue where it would not close when clicking outside of the modal. Here is the code for the modal: import Reac ...

Cross-origin resource sharing (CORS) seems to be creating a barrier for the communication between my Angular

During the process of developing an Angular and NestJS app with NGXS for state management, I encountered a CORS error while serving my application. The error message in the console indicated: Access to XMLHttpRequest at 'localhost:333/api/product-i ...

What is the best way to assign JSON values to my class property?

I've been working on a weather application that showcases the current weather of 5 different cities. By clicking on each city, users can access a detailed view displaying the 5-day forecast for that particular location. Currently, I have defined a we ...

Declaration in Typescript for an array of strings that will be returned as a

I am facing an issue with my async function that is supposed to return either a single string or an array of strings. Here is the relevant code snippet: async getAllAnnotationTimes(): Promise<string> | Promise<string[]> { return aw ...

Eliminating the parent property name from the validation message of a nested object

When using @ValidateNested() with the class-validator library, I encountered a formatting issue when validating a nested object: // Object Schema: export class CreateServerSettingsDTO { @IsNotEmpty({ message: 'Username is required' }) usernam ...

What could cause a member variable to be uninitialized in the ngInit method in Ionic/Angular, even though it was initially set in the constructor

Despite setting the modal form to be bound to an instance of "Foo" during its construction, I encountered a strange issue where, post-constructor, this.foo became undefined. Even after verifying this through breakpoints and console.log, the problem persist ...

Return true for cucumber datatable in typescript without fail

I am facing an issue where the following step definition always returns true even for incorrect data from the dataTable. Can someone assist me in correcting the syntax in TypeScript with Chai assertions? Then(/^Verify the following details in report$/, a ...

The length of video files created by MediaRecorder is not retained

This component prompts the user for camera access, displays a video preview, and allows the user to watch it again with video controls such as downloading or navigating to specific moments. However, there is an issue where the recorded video seems to be ...