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

Utilizing event bubbling in Angular: a comprehensive guide

When using Jquery, a single event listener was added to the <ul> element in order to listen for events on the current li by utilizing event bubbling. <ul> <li>a</li> <li>b</li> <li>c</li> <li>d< ...

Error message is not shown by React Material UI OutlinedInput

Using React and material UI to show an outlined input. I can successfully display an error by setting the error prop to true, but I encountered a problem when trying to include a message using the helperText prop: <OutlinedInput margin="dense&quo ...

Is there a way to prevent the URL of my Next.js app from constantly changing?

Our current Next.js project requires that the static URL remains constant, even when navigating between pages. This is a client requirement that we must adhere to. Can you provide suggestions on how we can achieve this? Maintaining the same URL throughout ...

Executing React's useEffect hook twice

As I work on developing an API using express.js, I have implemented an authentication system utilizing JWT tokens for generating refresh and access tokens. During testing with Jest, Supertest, and Postman, everything appears to be functioning correctly. O ...

Am I on track with this observation?

I am currently using the following service: getPosition(): Observable<Object> { return Observable.create(observer => { navigator.geolocation.watchPosition((pos: Position) => { observer.next(pos); observer.c ...

Definitions for TypeScript related to the restivus.d.ts file

If you're looking for the TypeScript definition I mentioned, you can find it here. I've been working with a Meteor package called restivus. When using it, you simply instantiate the constructor like this: var Api = new Restivus({ useDefaultA ...

What is the process for implementing a type hint for a custom Chai assertion?

As part of my typescript project, I decided to create a custom assertion for the chai assertion library. Here is how I implemented it: // ./tests/assertions/assertTimestamp.ts import moment = require("moment"); import {Moment} from "moment"; const {Asser ...

Creating a custom button for exporting a high chart to CSV

My Angular project involves exporting a chart to various formats, such as png, jpeg, pdf, and SVG. However, I am encountering an issue when trying to export the chart as CSV or . I have attempted the following code: this.lineChart.chart.downloadCSV(); //F ...

Having difficulty accessing certain code in TypeScript TS

Struggling with a TypeScript if else code that is causing errors when trying to access it. The specific error message being displayed is: "Cannot read properties of undefined (reading 'setNewsProvider')" Code Snippet if (this.newsShow != ...

What is the best way to separate a string using a comma as a delimiter and transform it into a string that resembles an array with individual string elements

I am in search of a way to transform a string, such as: "one, two, three, four" into a string like: "["one", "two", "three", "four"]" I have been attempting to devise a solution that addresses most scenarios, but so far, I have not been successful. The ap ...

Tracking user session duration on a React Native app can be achieved by implementing a feature that monitors and

I'm currently focusing on tracking the amount of time a user spends on the app in minutes and hours, and displaying this information. I have successfully implemented the functionality to count minutes, but I am struggling to figure out how to track wh ...

What is the best way to set up a reactive form in Angular using the ngOnInit lifecycle

I have been facing an issue while trying to set up my reactive form with an observable that I subscribed to. Within the form class template, I used the ngOnInit lifecycle hook to fetch the desired object, which is the product. The first code snippet repre ...

Discovering the JavaScript source file for a package using WebStorm and TypeScript

In my TypeScript project, there is a usage of Express with the following method: response.send('Hello'); I am interested in exploring the implementation of the send() method. However, when I try to navigate to the source code by ctrl+clicking o ...

Angular data binding with an object instead of an array list

Currently, I am implementing Angular and attempting to iterate through an object. Data in JSON format employee {"fName":"mike","email":"<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="ebb3b3b3b3b3b3b3b3ab83849f868a8287c588848 ...

What is the best way to compare an array with comma-separated values in JavaScript?

I have a scenario where I have two arrays, one for categories and the other for products. Each product contains multiple categories as a comma-separated string. My goal is to match a specific category with each product's product_category value and the ...

AmCharts stacked bar chart - dynamically adjust value visibility (adjust transparency) based on user interaction

I recently utilized amcharts to construct a bar chart. The creation of my stacked bar chart was inspired by this specific example. Currently, I am attempting to modify the alpha (or color) of a box when hovering over another element on my webpage, such as ...

Having difficulty creating a TypeScript function

I've encountered a TypeScript error that has left me puzzled: src/helpers.ts:11:14 - error TS2322: There's an issue with this piece of code and I can't quite grasp it: Type '<T extends "horizontal" | "vertical" | undefined, U extends ...

Can you explain the distinction between declaring a map in TypeScript using these two methods?

When working in TypeScript, there are two different ways to declare a map. The first way is like this: {[key:number]string} This shows an example of creating a map with keys as numbers and values as strings. However, you can also define a map like this: M ...

Issues with loading NextJS/Ant-design styles and JS bundles are causing delays in the staging environment

Hey there lovely folks, I'm in need of assistance with my NextJS and Ant-design application. The current issue is only occurring on the stagging & production environment, I am unable to replicate it locally, even by running: npm run dev or npm r ...

Enhancing Type Safety in TypeScript with Conditional Typing within Array reduce()

In my codebase, I've implemented a function named groupBy (inspired by this gist) that groups an array of objects based on unique key values provided an array of key names. By default, it returns an object structured as Record<string, T[]>, wher ...