Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

92
Views
Can you create a Typescript type that has exactly one of a set of properties AND is indexable by that set

I have the following types:

type OrBranch = {
   or: Branch[]
}

type AndBranch = {
   and: Branch[]
}

I'd like a type Branch that can be either an OrBranch or an AndBranch. So I first tried:

type Branch = AndBrand | OrBranch

Work great, unless I want to do something like:

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

let nodes = branch[andOr]

Then I get that branch isn't indexable. OK, so I try an indexable type:

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

But that requires that BOTH and and or exist, so I can't cast and AndBranch to Branch in this case.

Similarly

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

doesn't work, for the same reason.

While I could use type guards to determine the type, I have long functions that operate on these objects, in which they can be treated the same other than the property. I was hoping to eliminate a bunch of duplicate code by using the andOr variable, which type guards don't really prevent. For example:

let retval = {} as Branch;
if (isAnd(branch)) {  // branch is a Branch parameter passed in
   (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[]);

vs.

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 know there's a way to require exactly one of and and or (like the answer to Enforce Typescript object has exactly one key from a set). But that type is not indexable.

Is it possible to get both effects?

about 4 years ago · Juan Pablo Isaza
1 answers
Answer question

0

Making the type indexable won't solve the basic problem, which is that you're trying to use the property and on something that may be an and branch (and thus have the property) or may be an or branch (and thus not). Instead, ask the branch what it has and use that, because that allows TypeScript to narrow the type:

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

You could combine that with your andOr if you have code that thinks it knows what the branch is, perhaps by using a type assertion function:

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

Then:

if (andOr === "and") {
    assertIsAndBranch(branch);
    // ...here, TypeScript knows `branch` is an `AndBranch`...
}

Re your edit: If you have significant code that needs to deal with the items contained by AndBranch's and or OrBranch's or without knowing or caring whether they're AndBranch or OrBranch instances, I'd redesign them as a discriminated union where all members of the union have the same items (or whatever) property:

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

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

That way, the code that doesn't care what type of branch it is can work with items. The code you showed would be:

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

Playground link

Having properties do double-duty (both indicating the type of branch and the items within it) as they do in your current types makes writing typesafe code to handle the items without knowing/caring what type of branch it is really hard.

about 4 years ago · Juan Pablo Isaza Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!