What is the best way to specify a type for an object that may or may not contain a certain property?

I encountered a runtime TypeError that I believe should have been caught during compile time with TypeScript. Here is the code snippet:

type MyData = Record<string, Record<string, string>>;

function retrieveData(): MyData {
    return {
        sample: {
            info: "value",
        }
    };
}

const data = retrieveData();

const value = data.Section.Value; // TypeError: data.Section is undefined

In the above scenario, since MyObject is an object where keys are strings, TypeScript assumes that any string can be used as a key with the corresponding value being Record<string, string>. However, in reality, there might be a limited number of keys on an object unless explicitly defined using a getter to return a string for any other given key.

To address this issue, I attempted utilizing

Partial<Record>
. Although this type resolves the previous bug, it presents challenges when iterating over the object:

type MyData = Partial>;

function retrieveData(): MyData {
    return {
        sample: {
            info: "value",
        }
    };
}

const data = retrieveData();

Object.values(data).filter(val => val.Value); // val: Object is possibly undefined

The error mentioned above cannot occur during runtime because if a property exists on data, it will definitely be of type Record<string, string>. Unfortunately, I am uncertain how to convey this information using the type system.

Hence, my query is: How can I define a type for an object that

  • consists solely of string keys,
  • does not have defined properties for all strings,
  • and if a property is defined, the corresponding value is guaranteed to be Record<string, string>?

Edit: While specifying all keys in the type (like Record<"keya"|"keyb", string>) is feasible, it does not align with my requirement as I do not know all the keys in advance. What I seek is a type named PartialRecord where if data: PartialRecord<string, T>, then data.SOMEKEY has the type T | undefined (since the key SOMEKEY may not exist returning undefined) but Object.values(data) should yield T[], signifying that Object.values only returns existing values of type T.

Answer №1

Your initial MyObject type is satisfactory, however, there may be uncertainty when attempting to access a property on it without knowing if it is defined or not. To address this issue, optional chaining can be utilized.

const v = params?.Section?.Val;

If the Section does not exist within params, the value of v will be undefined, and the same applies to Val. It is essential to validate whether v is undefined before utilizing it.

If you wish to more precisely define which properties must exist, you can specify an object.

type MyObject = {
    test: Record<string, string>;
    [key: string]: Record<string, string> | undefined;
};

In this scenario, the test property is a mandatory property of type Record<string, string>, while any other property could be either Record<string, string> or undefined.

EDIT: Upon reevaluating your query and updated details, it appears that the functionality you seek can be enabled using the noUncheckedIndexedAccess flag in the tsconfig.json file. Additional information can be found at https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess

Answer №2

To ensure clarity, it is important to enumerate all potential keys that your object may possess, as demonstrated below:

type MyObject = Record<"test" | "Section" | "foo", Record<string, string>>;

function getParameters(): Pick<MyObject, "test">
{
    return {
        test: {
            test: "val",
        }
    };
}

const params = getParameters();

const v = params.Section.Val; // Property 'Section' does not exist on type 'Pick<MyObject, "test">'

https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys

If preferred, you can declare all keys separately in a variable like so:

type keys = "test" | "Section" | "foo";
type MyObject = Record<keys, Record<string, string>>;

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

How can we initiate an AJAX request in React when a button is clicked?

I'm fairly new to React and I'm experimenting with making an AJAX call triggered by a specific button click. This is how I am currently using the XMLHttpRequest method: getAssessment() { const data = this.data //some request data here co ...

Utilizing Angular2 with Webpack in Visual Studio 2015

Is there a way to utilize Visual Studio 2015 alongside Webpack and Angular2? I have successfully created an Angular2 App with VS, but now that I've added Webpack to build my app, I would like to debug all of my code using IIS Express. I want to be abl ...

What changes can be implemented to convert this function to an asynchronous one?

Is it possible to convert the following function into an asynchronous function? getHandledSheet(): void { this.timesheetService.getAllTimesheets().subscribe({next: (response: TimeSheet[]) => {this.timesheetsHandled = response.filter(sheet => ...

Wait for the product details to be fetched before returning the products with a Firestore promise

Although I know similar questions have been asked numerous times before, I am struggling with something that seems quite straightforward to me. We have two tables - one called "order_lines" and the other called "order_lines_meta". My goal is to query the " ...

Getting a multidimensional array from JSON in Typescript: A step-by-step guide

Below is a sample of a JSON array containing information about cars. var cars = { "cars": { "john": [], "alex": [ "ford" ], "hilton": [], "martin ...

Customizing font color upon hover in Next.js and Tailwind.css

Recently, I developed a Navbar component that displays a purple link when navigating to pages like Home or Projects. The issue arises when the background color is light; in this case, the link turns green on hover instead of staying purple. How would I adj ...

TypeScript overload does not take into account the second choice

Here is the method signature I am working with: class CustomClass<T> { sanitize (value: unknown): ReturnType<T> sanitize (value: unknown[]): ReturnType<T>[] sanitize (value: unknown | unknown[]): ReturnType<T> | ReturnType< ...

"Utilize a callback function that includes the choice of an additional second

Concern I am seeking a basic function that can receive a callback with either 1 or 2 arguments. If a callback with only 1 argument is provided, the function should automatically generate the second argument internally. If a callback with 2 arguments is s ...

The issue of declaration merging and complications with nested node_modules

Here is the structure I am working with: @my/app node_modules @types/angular @types/angular-translate @my/library node_modules @types/angular The issue arises from the fact that @types/angular-translate extends the definitions of @types/angular ...

Challenges arise with data updating following a mutation in @tanstack/react-query

As I work on building an e-commerce website using React, I have a specific feature where users can add products to their favorites by clicking a button. Following this action, I aim to update the profile request to display the user's information along ...

Where can I find the Cypress.json file for Angular integration with Cypress using Cucumber?

We are currently transitioning from Protractor to Cypress utilizing Cucumber with the help of cypress-cucumber-preprocessor. While searching for Angular documentation on this setup, including resources like , all references lead to an automatically generat ...

Guide to generating a text string by utilizing the foreach loop

Is there a way to combine text strings from interfaces into a single file for display in UI? The current code is generating separate files for each interface. How can I achieve the expected result of having all interfaces in one file? Additionally, is it ...

How can you enhance a component by including additional props alongside an existing onClick function?

As a newcomer to React and TypeScript, I've created a simple component that looks like this: const CloseButton = ({ onClick }: { onClick: MouseEventHandler }) => { const classes = useStyles(); return <CloseIcon className={classes.closeButto ...

Unable to showcase the content inside the input box

I am having trouble displaying a default value in an input field. Here is how I attempted to do it: <input matInput formControlName="name" value="Ray"> Unfortunately, the value is not appearing as expected. You can view my code o ...

Utilizing trackingjs as an external library in Ionic2

I have been attempting to incorporate the trackingjs library (https://www.npmjs.com/package/tracking) into my ionic2 project. Following the guidelines in the documentation (https://ionicframework.com/docs/v2/resources/third-party-libs/), I was able to suc ...

Exploring Angular 8 Route Paths

Working on an Angular 8 project, I encountered an issue with my code: src/app/helpers/auth.guard.ts import { AuthenticationService } from '@app/services'; The AuthenticationService ts file is located at: src/app/services/authentication.servic ...

Using Node.js with Typescript and RedisJSON allows for a powerful and efficient

I've recently started delving into nodejs, typescript, and redis for programming. However, I've encountered an issue with redis: when defining a data interface to be stored in redis, the system throws errors as soon as I try to specify the data t ...

Typescript is missing Zod and tRPC types throughout all projects in the monorepo, leading to the use of 'any'

Recently, I've found myself stuck in a puzzling predicament. For the last couple of weeks, I've been trying to troubleshoot why the types are getting lost within my projects housed in a monorepo. Even though my backend exposes the necessary types ...

Adding FormControl dynamically to FormGroup can be achieved by simply using the appropriate method

Currently, I am working with a plunker where I am dynamically creating form components based on the model specified in app.ts. However, I am facing an issue where I cannot add formControlName = "name" to the component. In my control-factory.directive.ts ...

How to upload files from various input fields using Angular 7

Currently, I am working with Angular 7 and typescript and have a question regarding file uploads from multiple input fields in HTML. Here is an example of what I am trying to achieve: <input type="file" (change)="handleFileInput($event.target.files)"&g ...