An alternative to the Javascript switch statement
Tags: javascript, designpatterns, coding, programming
Published: 2024-01-22 02:14:59

This article expands on Todd Motto's idea of [Replacing switch statements with Object literals](https://ultimatecourses.com/blog/deprecating-the-switch-statement-for-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 that I crafted 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.

```Javascript
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');
}
```
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.

```Javascript
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
```

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.

```Javascript
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
```

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.

```Javascript
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
```

## 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.

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

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

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

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

```Javascript
const switchCase = ({cases, defaultCase}) => {
    return (expression) => {
        return (cases[expression] || defaultCase)();
    }
}
```

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

```Javascript
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
```

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.

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

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

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.

```Typescript
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)();
    }
}
```

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.

```Typescript
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
```

## All you need in one place

### Javascript version

Utility Function:

```Javascript
const switchCase = ({cases, defaultCase}) => (expression) => (cases[expression] || defaultCase)();
```

Usage:

```Javascript
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
```

### Tyepscript version

Utility Function:

```Typescript
const switchCase = <T,>({cases, defaultCase}: {cases: {[key: string]: () => T}, defaultCase: () => T}) => <S,>(expression: S) => (cases[expression as string] || defaultCase)();
```

Usage:

```Typescript
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
```