DEV Community

Cover image for An alternative to the Javascript switch statement
Mohamad Jawhar
Mohamad Jawhar

Posted on

An alternative to the Javascript switch statement

This article expands on Todd Motto's idea of Replacing switch statements with Object literals. A very neat and beautiful alternative to the outdated, clunky and verbose switch statement. By the end of the article you will be provided with a utility function based on Todd's solution that is much more developer friendly, so stick to the end!

If you're not interested in the know-how and just want the utility function, scroll down to the last section (All you need in one place).

So what's wrong with the switch statement?

While the switch statement can be useful in certain situations, many argue that it's not Javscript's best design for what it's good for. It is less flexible, less readable and less maintainable than other constructs.

For example, one of the main criticisms of the switch statement is its fall-through behavior. If you forget to include a break statement at the end of a case, the control will fall through to the next case, leading to unintended behavior as shown in the example below. This can make the code more error-prone and harder to maintain.

switch (fruit) {
  case 'apple':
    console.log('Apple selected');
    // Missing break statement, falls through to the next case
  case 'orange':
    console.log('Orange selected');
    break;
  case 'banana':
    console.log('Banana selected');
    break;
  default:
    console.log('Unknown fruit');
}
Enter fullscreen mode Exit fullscreen mode

In this example, if fruit is 'apple', both "Apple selected" and "Orange selected" will be logged.

The alternative Object Literal lookups

Compared to switch statements, Object Literals are more flexible and expressive.

here's how we can use them to return string values only.

const getDate = (unit) => {
  var date = {
    'year': '2024',
    'month': 'January',
    'day': '21',
    'default': 'Default value'
  };
  return (date[unit] || date['default']);
}

var month = getDate('month');
console.log(month); // January
Enter fullscreen mode Exit fullscreen mode

Sometimes we need to write more complex code and just returning a string is not enough. We can take the above code a step further and use functions instead of strings in which we can include more complex code.

const getDate = (unit) => {
  var date = {
    'year': () => {
        // do more complicated stuff here
        // just returning a string in this case
        return '2024';
    },
    'month': () => {
        return 'January';
    },
    'day': () => {
        return '21';
    },
    'default': () => {
        return 'Default value'
    }
  };
  // we return the Object literal's function invoked
  return (date[unit] || date['default'])();
}

var month = getDate('month');
console.log(month); // January
Enter fullscreen mode Exit fullscreen mode

But what if we want a fall through behavior? We can easily implement that with object literals, and it's more readable, declarative and less prone to errors. It also doesn't involve adding or removing break which is what we're looking for.

const getDayType = (day) => {
  const isWeekDay = () => {
      return 'Weekday';
  }
  const isWeekEnd = () => {
      return 'Weekend';
  }
  var days = {
    'monday': isWeekDay,
    'tuesday': isWeekDay,
    'wednesday': isWeekDay,
    'thursay': isWeekDay,
    'friday': isWeekDay,
    'saturday': isWeekEnd,
    'sunday': isWeekEnd,  
    'default': () => {
        return 'Default value'
    }
  };
  // we return the Object literal's function invoked  
  return (days[day] || days['default'])();
}

var dayType = getDayType('sunday');
console.log(dayType); // WeekEnd
Enter fullscreen mode Exit fullscreen mode

Turning what we learned into a utility function

Now that we learned how to use Object Literals instead of switch, let's build a utility function based on what we learned to simplify our lives even further.

Let's call our function switchCase. It receives an object with 2 properties: cases and defaultCase. Cases is the object literal which will hold our cases, and defaultCase is... well, the default case.

const switchCase = ({cases, defaultCase}) => {

}
Enter fullscreen mode Exit fullscreen mode

switchCase is a Higher Order Function that returns a Callback Function. The Callback Function receives the switch expression.

const switchCase = ({cases, defaultCase}) => {
    return (expression) => {

    }
}
Enter fullscreen mode Exit fullscreen mode

Now all the Callback Function needs to do is return the Object Literal's function invoked.

const switchCase = ({cases, defaultCase}) => {
    return (expression) => {
        return (cases[expression] || defaultCase)();
    }
}
Enter fullscreen mode Exit fullscreen mode

That's it! Now let's see an example of how we can use it.

let date = new Date();

const today = switchCase({
    cases: {
        year: () => date.getFullYear(),
        month: () => date.getMonth() + 1,
        day: () => date.getDate()
    },
    defaultCase: () => date
});

today('year'); // current year
today('month'); // current month
today('day'); // current day
today('century'); // default case - returns the current date Object
Enter fullscreen mode Exit fullscreen mode

For typescript users, we can make use of generics to allow users who are going to call the function later on to specify the type that they want the Object Literal Functions to return.

type SwitchCase<T> = {
    cases: {[key: string]: () => T},
    defaultCase: () => T
} 

const switchCase = <T,>({cases, defaultCase}: SwitchCase<T>) => {
    return (expression: string) => {
        return (cases[expression] || defaultCase)();
    }
}
Enter fullscreen mode Exit fullscreen mode

We can also add generics to the expression instead of assigning it a string type so we get intellisense from Typescript in case we later decide to assign our own literal types.

type SwitchCase<T> = {
    cases: {[key: string]: () => T},
    defaultCase: () => T
} 

const switchCase = <T,>({cases, defaultCase}: SwitchCase<T>) => {
    return <S,>(expression: S) => {
        return (cases[expression as string] || defaultCase)();
    }
}
Enter fullscreen mode Exit fullscreen mode

Ant this is how we use it. Note that we don't always have to specify the type since Typescript automatically infers it unless it's a union of multiple types as shown below. Moreover, adding the literal types in TimeUnit is optional and we don't have to pass it, although we lose the intellisense from Typescript if we don't.

type TimeUnit = 'year' | 'month' | 'day';

let date = new Date();

const today = switchCase<number | Date>({
    cases: {
        year: () => date.getFullYear(),
        month: () => date.getMonth() + 1,
        day: () => date.getDate()
    },
    defaultCase: () => date
})<TimeUnit>;

today('year'); // current year
today('month'); // current month
today('day'); // current day
today('century'); // default case - returns the current date Object
Enter fullscreen mode Exit fullscreen mode

All you need in one place

Javascript version

Utility Function:

const switchCase = ({cases, defaultCase}) => (expression) => (cases[expression] || defaultCase)();
Enter fullscreen mode Exit fullscreen mode

Usage:

let date = new Date();

const today = switchCase({
    cases: {
        year: () => date.getFullYear(),
        month: () => date.getMonth() + 1,
        day: () => date.getDate()
    },
    defaultCase: () => date
});

today('year'); // current year
today('month'); // current month
today('day'); // current day
today('century'); // default case - returns the current date Object
Enter fullscreen mode Exit fullscreen mode

Tyepscript version

Utility Function:

const switchCase = <T,>({cases, defaultCase}: {cases: {[key: string]: () => T}, defaultCase: () => T}) => <S,>(expression: S) => (cases[expression as string] || defaultCase)();
Enter fullscreen mode Exit fullscreen mode

Usage:

type TimeUnit = 'year' | 'month' | 'day';

let date = new Date();

const today = switchCase<number | Date>({
    cases: {
        year: () => date.getFullYear(),
        month: () => date.getMonth() + 1,
        day: () => date.getDate()
    },
    defaultCase: () => date
})<TimeUnit>;

today('year'); // current year
today('month'); // current month
today('day'); // current day
today('century'); // default case - returns the current date Object
Enter fullscreen mode Exit fullscreen mode

Top comments (0)