Set certain properties within the nested object to be optional

Within a TypeScript project, there exists a type definition that looks like this:

type Bar = {
  x: string;
  y: string;
  data: {
    z: string;
    w: string;
  };
};

This type is imported and widely used throughout the project, making it impossible for me to make any changes to it.

I am interested in creating a function that accepts a Bar as its parameter, with x and data.z being optional since I can provide default values for these fields within the context of this function:

function performAction(bar: {
  x?: string;
  y: string;
  data: {
    z?: string;
    w: string;
  };
}): void {
  // carry out necessary actions
}

The actual type is much more complex and undergoes updates occasionally, therefore directly copying its definition to add optional properties is not a feasible solution.

Here are some methods I have attempted:

Bar & { x?: string; data: { z?: string } }

This approach does not work as it combines the Bar type with the specified optional properties. Essentially, this results in just the original Bar type due to the required properties taking precedence.

Exclude<Bar, { x?: string; data: { z?: string } }>

This method fails as Bar is not a type union, and Exclude can only eliminate types from unions. It cannot remove individual properties from a type defined by a literal like in this case.

Partial<Bar>

This makes all properties of Bar optional, which is not desired in my situation.

Omit<Bar, 'x'> & { x?: string }

Omit successfully removes x from Bar, allowing me to include x back as an optional property. However, addressing the nested z within data proves to be a challenge.

What would be the most effective approach to tackle this issue?

Answer №1

You have the ability to create a type based on Foo for the doSomething parameter, all without altering Foo. TypeScript's type system is structural, meaning it focuses on the shape of types rather than their names or identities. This allows you to make Foo compatible with your new type, enabling you to pass arguments of type Foo to doSomething, while also being able to pass arguments of your custom type.

One approach to defining the new type involves extracting necessary information from Foo and adjusting properties like a and metadata.c:

type SpecialFoo = Omit<Foo, "a" | "metadata"> & {
    a?: Foo["a"];
    metadata: Omit<Foo["metadata"], "c"> & {
        c?: Foo["metadata"]["c"];
    };
};

Subsequently, doSomething can accept instances of this new type:

function doSomething(foo: SpecialFoo): void {
    // perform actions
    console.log(foo);
}

The following examples demonstrate valid usage:

// Valid
doSomething({ b: "b", metadata: { d: "d" } });
// Valid
doSomething({ a: "a", b: "b", metadata: { c: "c", d: "d" } });

On the contrary, providing incorrect values for a and c lead to errors, enforcing data integrity:

// Error expected due to incorrect type for `a`
doSomething({ a: 42, b: "b", metadata: { d: "d" } });
// Error expected due to incorrect type for `c`
doSomething({ b: "b", metadata: { c: 42, d: "d" } });

Code Playground link

Answer №2

We have the option to utilize the MergeDeep utility type provided by type-fest:

This utility allows for merging two objects or arrays/tuples recursively into a new type.

Although not explicitly stated in the documentation, the optional flags denoted by ? in the source types are also retained.

In specific scenarios, such as when building optional nested properties within a complex object, this simplifies the process:

type Bar = {
    a?: string;
    metadata: {
        c?: string;
    }
};

type SpecialFoo = MergeDeep<Foo, Bar>;
//   ^? { b: string; a?: string | undefined; metadata: { d: string; c?: string | undefined; }; }

Playground Link


It might be appealing to represent the optional nested keys as a union of paths (e.g.

"a" | "metadata.c"
), rather than defining the Bar type as shown above. This approach eliminates the need to redefine the value types.

This becomes even more desirable considering that type-fest already includes useful utility types like PickDeep and Paths.

However, configuring only the final part of these paths as optional is not straightforward: simply using PartialDeep makes all intermediate keys optional, which may not be desired. In the example shared, the key metadata remains mandatory:

type SetOptionalDeep<T, PathUnion extends Paths<T>> =
    MergeDeep<T, PartialDeep<PickDeep<T, PathUnion>>>;

type SpecialFoo = SetOptionalDeep<Foo, "a" | "metadata.c">;
//   ^? { b: string; a?: string | undefined; metadata?: { d: string; c?: string | undefined } | undefined; }

function doSomething(foo: SpecialFoo): void {}

// Generating an error, as expected: the presence of "metadata" is required
doSomething({ b: "b" });

Playground Link

In cases like the one presented, where the key metadata is at the top level, we can easily make it mandatory again using SetRequired:

type SpecialFoo = SetRequired<SetOptionalDeep<Foo, "a" | "metadata.c">, "metadata">;
//   ^? { b: string; a?: string | undefined; metadata: { d: string; c?: string | undefined; }; }

// As intended, an error occurs since inclusion of "metadata" is compulsory
doSomething({ b: "b" });

Playground Link

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

Angular's custom validator consistently returns a null value

I need help with validating the uniqueness of a username field in a form where an administrator can create a new user. I have implemented a uniqueUserNameValidator function for this purpose, but it always returns null. I suspect that the issue lies in the ...

Filter the angular accordion by passing a simple array value into the input

I am looking to filter my array of accordion items based on the value of the question matching the input I provide. I have tried using the filter method for this. this.accordionItems = [ { "topic":"polizze", " ...

Steps for confirming a property setting on an interface

I am working with the following interface export interface Command { id: CommandId; disabled: boolean; } My goal is to verify that the 'disabled' property has been changed. Here are my attempts: 1) Creating an object and checking if t ...

What is the best way to iterate through an array of arrays using a foreach loop to calculate the total number of specific properties?

For instance, if YieldCalcValues were to look something like this: [ [ 850, 500 ], [ 3, 6 ], [ 1200, 5000 ], [ 526170, 526170 ] ] I am looking to create a foreach loop that calculates the yield per for each product. How can I accomplish this correctly? l ...

Divide the code into individual components within Angular 2 projects

I currently have 3 Angular 2 projects developed in TypeScript. Each project contains the same models and services. I would like to find a way to integrate these common elements at a global level and connect them with each individual project. Any suggesti ...

The error at core.js:4002 is a NullInjectorError with a StaticInjectorError in AppModule when trying to inject FilterService into Table

While exploring PrimeNg Table control in my application - as a beginner in PrimeNg & Angular, I encountered an error No provider for FilterService! shown below: core.js:4002 ERROR Error: Uncaught (in promise): NullInjectorError: StaticInjectorError(AppMo ...

Launching Node Application

While working with NestJS and IIS, I encountered an issue when deploying my 'dist' folder on the server using IISNode. The error message 'module not found @nestjs/core' prompted me to install the entire 'package.json' files (n ...

Issue with setting value using setState in TypeScript - what's the problem?

Every time I attempt to update the value of currentRole, it appears highlighted in red. Here is a screenshot for reference: const Container: React.FC<ContainerProps> = ({ children }) => { const [role, setRole] = useState<string>(); useE ...

Converting JSON array to custom object array in TypeScript

As a newcomer to this area, please excuse any inaccuracies in my language. Don't hesitate to ask for more details if needed. I currently have some TypeScript interfaces: export interface Item { id: string type: string state: string } ex ...

Automatically compile files while performing an npm install or update

I am looking for a way to automatically compile my TypeScript code into JavaScript when another project requires it. For example, when a project runs npm install or updates with my project as a dependency, I want a specific command to be executed after all ...

The module 'contentlayer/generated' or its type declarations are missing and cannot be located

Currently running NextJS 13.3 in my application directory and attempting to implement contentlayer for serving my mdx files. tsconfig.json { "compilerOptions": { ... "baseUrl": ".", "paths" ...

Guidelines for implementing more rigorous type checks in TypeScript:

I am looking to enhance the code validation process and eliminate any implicit 'any' types. To achieve this, I have set "strict": true in my tsconfig.json. { "compilerOptions": { "target": "ES5", ...

What is the best way to ensure type safety in a Promise using Typescript?

It seems that Promises in Typescript can be type-unsafe. This simple example demonstrates that the resolve function accepts undefined, while Promise.then infers the argument to be non-undefined: function f() { return new Promise<number>((resolve) ...

Is it possible to display Angular Material Slider after the label?

Searching through the Angular Material docks, I came across the Sliders feature. By default, the slider is displayed first, followed by its label like this: https://i.sstatic.net/C5LDj.png However, my goal is to have the text 'Auto Approve?' sh ...

Is there a way to modify a suffix snippet and substitute the variable in VS Code?

While working on my Java project in VS Code, I stumbled upon some really helpful code snippets: suffix code snippets Once I type in a variable name and add .sysout, .cast, or similar, the snippet suggestion appears. Upon insertion, it translates to: res ...

Having trouble retrieving object property despite returning an object type - React with Typescript

I am facing a issue with my React state where I have an array of objects and I am adding an object to it using the setState method. The objects are successfully added to the array. However, when I try to access the properties of the object in another func ...

What is the solution to the error message stating that the property 'pipe' is not found on the OperatorFunction type?

I implemented the code based on RxJS 6 documentation while working with angular 5, RxJS 6 and angularfire2 rc.10. However, I encountered the following error: [ts] property 'pipe' does not exist on type 'OperatorFunction<{}, [{}, user, str ...

Patience is key: Techniques for waiting on service responses in Angular 2

Here is the code snippet I have been working on: canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { let isAuthenticated: boolean = false this.authServiceLocal.isAuthenticated().then(response => isAuthenticated = r ...

What is the proper way to utilize a service within a parent component?

I need assistance with setting up inheritance between Child and Parent components. I am looking to utilize a service in the Parent component, but I have encountered an issue. When attempting to input the service in the Parent constructor like this: expor ...

Typescript enables bidirectional control of Swiper

I attempted to use the two-way control slider example from Swiper documentation, but I encountered TypeScript errors that prevented it from working correctly. Is there a way to make it compatible with TypeScript? The specific errors I received were: TS23 ...