Home > Software design >  Filter object array where name is union type
Filter object array where name is union type

Time:01-20

I have an array i want to filter with typescript, however i can't get the typescript validation side working, i have an array of actions, and i want to filter out the internal actions (see below) - however i cannot tell typescript that the filteredActions array no longer contains internalAction!

Here's what i tried:

type ActionName = 'replaceText' | 'replaceImage' | 'internalAction';

interface Action {
  name: ActionName
}
const INTERNAL_ACTIONS = ['internalAction'] as const;
type InternalActions = typeof INTERNAL_ACTIONS[number];

const actions: Action[] = [{
   name: 'replaceText'
}, {
   name: 'replaceImage'
}, {
   name: 'internalAction'
}];
const isInternalAction = (name: ActionName): name is Exclude<ActionName, InternalActions> => name in INTERNAL_SLIDE_ACTIONS;
const filteredActions = actions.filter((action) => !isInternalAction(action.name));
for (const action of filteredActions) {
  if (action.name === 'internalAction') {
    // i'd expect TS to throw an error here as internalAction should never appear
  }
  if (action.name === 'replaceImage') {
    // i'd expect TS to NOT throw an error here
  }
}

CodePudding user response:

the issue is in isInternalAction arrow function.

in order to check if an element in an array in typescript you cannot use in operator, but you can use: includes, some, find, indexOf (maybe there is other options but these are on the top of my head)

so I would change your code to for example:

type ActionName = 'replaceText' | 'replaceImage' | 'internalAction';

interface Action {
  name: ActionName
}
const INTERNAL_ACTIONS: readonly ActionName[] = ['internalAction'] as const;
type InternalActions = typeof INTERNAL_ACTIONS[number];

const actions: Action[] = [{
  name: 'replaceText'
}, {
  name: 'replaceImage'
}, {
  name: 'internalAction'
}];

// use includes to check if element exists in an array
const isInternalAction = (name: ActionName): boolean => INTERNAL_ACTIONS.includes(name);
const filteredActions = actions.filter((action) => !isInternalAction(action.name));

console.log(filteredActions)

here you can find and run the snippet above in a ts-playground

CodePudding user response:

The trick was the filter, I had to rebind the action with the names excluded:

const filteredActions = slide.actions.filter((action): action is Omit<Action, 'name'> & {
    name: Exclude<ActionName, InternalActions>
} => typeof INTERNAL_ACTIONS.find(i => i === action.name) === 'undefined');

This way each action after filtering has the correct typings

  • Related