Is it possible to design a Typescript type that only contains one property from a defined set and is indexable by that set as well?

I have the different types listed below:

type OrBranch = {
   or: Branch[]
}

type AndBranch = {
   and: Branch[]
}

I need a type called Branch that can either be an OrBranch or an AndBranch. I initially attempted this:

type Branch = AndBrand | OrBranch

This works well, unless I want to perform something like:

let branch: Branch = ...
let andOr = 'and';   // or 'or'

let nodes = branch[andOr]

In this case, I receive an error stating that branch is not indexable. So, I then tried using an indexable type:

type AndOr = 'and' | 'or';
type Branch = Record<AndOr, Branch[]>;

However, this approach demands that both and and or must exist, preventing me from casting an AndBranch to Branch in certain scenarios.

Similarly,

type Branch = Record<AndOr, Branch[]> | AndBranch | OrBranch

This solution also doesn't work for the same reason.

Although I could utilize type guards to determine the type, I have lengthy functions that manipulate these objects where they can essentially be treated the same except for the property. Hence, I wanted to minimize redundant code by leveraging the andOr variable, which type guards do not fully prevent. For example:

let retval = {} as Branch;
if (isAnd(branch)) {  
   (retval as AndBranch).and = [] as Branch[];
   set = (retval as AndBranch).and;
} else {
   (retval as OrBranch).or = [] as Branch[];
   set = (retval as OrBranch).or;
}

set = _.reduce(set, (all, item: Branch)=> {
   if (isAnd(branch) && isAnd(item)) 
      return _.union(all, item.and);
   else if (isOr(branch) && isOr(item)) 
      return _.union(all, item.or);
   else 
      return all;
}, [] as Branch[]);

in comparison to:

andOr = isAnd(branch) ? 'and' : 'or';
let retval = {} as Branch;
retval[andOr] = _.reduce(set, (all, item: Branch) => {
   if (item[andOr]) 
      return _.union(all, item[andOr]);
   else
      return all;
}, [] as Branch[]);

I am aware of a method to mandate exactly one of and and or (similar to the solution provided in Enforce Typescript object has exactly one key from a set). However, that type cannot be indexed.

Is there a way to achieve both effects?

Answer №1

By making the type indexable, the fundamental issue remains unresolved. This problem arises when attempting to utilize the property and on a branch that could either be an and branch (possessing the property) or an or branch (lacking the property). The recommended approach is to inquire about the branch's content and then utilize that information for narrowing down the type in TypeScript:

if ("and" in branch) {
    // ...use `branch.and`...
} else {
    // ...use `branch.or`...
}

If necessary, you can combine this with your existing andOr logic by incorporating a type assertion function:

function assertIsAndBranch(branch: Branch): asserts branch is AndBranch {
    if (!("and" in branch)) {
        throw new Error(`branch is not an AndBranch`);
    }
}
// (Also, include `assertIsOrBranch`)

Subsequently:

if (andOr === "and") {
    assertIsAndBranch(branch);
    // At this point, TypeScript recognizes `branch` as an `AndBranch`...
}

In response to your recent edit: If substantial code needs to handle the contents of AndBranch's and or OrBranch's or items without distinguishing between them, consider restructuring them as a discriminated union. In this setup, all union members share a common items (or similar) property:

type OrBranch = {
    type: "or";
    items: Branch[];
};

type AndBranch = {
    type: "and";
    items: Branch[];
};

This way, code interested only in the branch's items regardless of its type can operate directly on items. Your specified code transformation would appear as follows:

const items = _.reduce(branch.items, (all, item) => {
    return _.union(all, item.items);
}, [] as Branch[]);
const retval = {type: branch.type, items};

Playground link

Using properties for dual purposes (indicating both the branch type and its contained items) as seen in your current types can complicate the development of type-safe code to handle the items independently of the branch type.

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

Tips for loading images dynamically (or lazily) as they come into the user's view with scrolling

Many modern websites, such as Facebook and Google Image Search, display images below the fold only when a user scrolls down the page enough to bring them into view (even though the page source code shows X number of <img> tags, they are not initially ...

Using AngularJS ng-model to select options in a dropdown menu with WebDriver

In the following code snippet, an AngularJS based dropdown menu is implemented: <select _ngcontent-c1="" class="form-control ng-pristine ng-valid ng-touched"> After opening the list, I attempted to select a variable from this list using the code be ...

Is it possible for you to enter "1.00" instead of just 1 when using the input type as number?

I am using Polymer paper-input and I would like to ensure that my input with type "number" always displays 2 decimal points, even when it is a whole number. Instead of just displaying as "1", I want it to be shown as "1.00" at all times. I have tried sett ...

Rearrange and place items at the dropped location

I am looking to create a drag-and-drop feature for moving items from a list of products to an empty container. Upon completion, I need to save the location and the selected items in a database so that I can recall this layout later. However, I have encoun ...

A step-by-step guide on retrieving information from Material UI components and incorporating an onSubmit feature to transmit data to the backend server

I've recently started working with react/material-UI. While working on a project, I turned to youtube videos and various resources for guidance. I opted for material-UI due to its user-friendly nature. However, I'm currently facing a challenge ...

Data vanishing in upcoming authentication session in test environment

I have encountered an issue with next auth in my next.js project. During development, the session data is lost if the server refreshes or if I switch to another tab and return to it. This forces me to sign out and then sign back in to restore the session d ...

We are experiencing difficulties rendering flash messages in Expressjs with Handlebars(hbs) and express-messages using flash-connect

I'm working on an express app and I want to display flash messages when a user signs up using a form. The technologies I am utilizing include Node.js, Express.js, Handlebars(hbs), connect-flash, and express-messages. To make finding errors easier, I ...

javascript issue with fetching data from URL using the GET method

Here is my attempt to fetch a JSON file from a server using asynchronous JavaScript promises. I am experiencing an issue where the result is undefined when using a specific URL, but it works when I use a different URL. Below is the working code with a dif ...

Exporting Data and Utilizing a Steady Data Table

I have incorporated the Fixed Data Grid into my latest project. https://facebook.github.io/fixed-data-table/example-sort.html My goal is to generate csv and pdf reports from the data displayed on the grid. Could you please advise me on how to export gri ...

Is there a way to tally the checked mat-radio-buttons in Angular?

I am seeking a way to determine the number of radio buttons checked in a form so I can save that number and transfer it to a progress bar. <mat-radio-group name="clientID" [(ngModel)]="model.clientID"> <mat-radio-button *ngFor="let n of CONST ...

Implementing Conditional Display of Span Tags in React Based on Timer Duration

In my current React project, I am facing an issue with displaying a span tag based on a boolean value. I need assistance in figuring out how to pass a value that can switch between true and false to show or hide the span tag. I tried two different methods ...

What is the best way to iterate through a JSON file?

Looking at my JSON file: { "stats": { "operators": { "recruit1": { "won": 100, "lost": 50, "timePlayed": 1000 }, "recruit2": { "won": 200, ...

Error Message: "Unable to locate module for Angular 5 UI Components packaging"

In the process of developing UI Components to be used in various web projects throughout the company, we are aiming to publish these components as an npm package on our local repository. It is crucial for us to include the sources for debugging purposes. F ...

Encountering compilation issues when transitioning from Angular 7 to Angular 8

Upon upgrading my project to Angular 8, an unexpected error occurs during the build process: ERROR in HostResourceLoader: loader(C:/myapp/cli/src/app/pages/user-home/user-home.component.html) returned a Promise i 「wdm」: Failed to compile. Ho ...

After clicking on the checkbox, req.body.task becomes undefined

Whenever I click on the checkbox, the value of req.body.task returns as undefined. <input type="checkbox" name="task" autocomplete="off" checked="" onchange="onToDochange({{id}})"> This function is triggered by a change event on the checkbox and it ...

Choosing a single item from multiple elements in React using React and typescript

In this particular project, React, TypeScript, and ant design have been utilized. Within a specific section of the project, only one box out of three options should be selected. Despite implementing useState and toggle functionalities, all boxes end up bei ...

The functionality of AC_FL_RunContent is failing after an UpdatePanel postback

In the code for the repeater item, I have a JavaScript function that calls AC_FL_RunContent to display a flash file when a link within the repeater item is clicked. The datasource I am using displays the first page of video links with five items per page, ...

Tips for concealing XHR Requests within a react-based single page application

Is there a way to hide the endpoint visible in Chrome's devtools under the network tab when data is fetched in React? Can server-side rendering solve this issue? ...

fakeAsync failing to synchronize with async task completion

Scenario In my testing process, I am evaluating a component that utilizes an observable-based service to retrieve and display data for internationalization purposes. The i18n service is custom-made to cater to specific requirements. While the component ...

Simply use `$timeout` inside the `$watch` method in AngularJS to create a chained

My goal is to link two $timeout functions inside a $watch. This $watch monitors user actions, and if any action is detected, both $timeout instances are canceled. Below is the code snippet outlining this scenario. .run(['$rootScope', '$loc ...