227

How do I write a switch for the following conditional?

If the url contains "foo", then settings.base_url is "bar".

The following is achieving the effect required but I've a feeling this would be more manageable in a switch:

var doc_location = document.location.href;
var url_strip = new RegExp("http:\/\/.*\/");
var base_url = url_strip.exec(doc_location)
var base_url_string = base_url[0];

//BASE URL CASES

// LOCAL
if (base_url_string.indexOf('xxx.local') > -1) {
    settings = {
        "base_url" : "http://xxx.local/"
    };
}

// DEV
if (base_url_string.indexOf('xxx.dev.yyy.com') > -1) {
    settings = {
        "base_url" : "http://xxx.dev.yyy.com/xxx/"
    };
}
Penny Liu
  • 15,447
  • 5
  • 79
  • 98
Dr. Frankenstein
  • 4,634
  • 7
  • 33
  • 48

10 Answers10

398

If you're happy that your regex at the top is stripping away everything that you don't want to compare in your match, you don't need a substring match, and could do:

switch (base_url_string) {
    case "xxx.local":
        // Blah
        break;
    case "xxx.dev.yyy.com":
        // Blah
        break;
}

...but again, that only works if that's the complete string you're matching. It would fail if base_url_string were, say, "yyy.xxx.local" whereas your current code would match that in the "xxx.local" branch.

Otherwise, while you can use a switch for substring matching, but I wouldn't recommend it in most situations (more below). Here's how it would look:

function test(str) {
    switch (true) {
        case /xyz/.test(str):
            console.log("• Matched 'xyz' test");
            break;
        case /test/.test(str):
            console.log("• Matched 'test' test");
            break;
        case /ing/.test(str):
            console.log("• Matched 'ing' test");
            break;
        default:
            console.log("• Didn't match any test");
            break;
    }
}

function test(str) {
    console.log("Testing '" + str + "':");
    switch (true) {
        case /xyz/.test(str):
            console.log("• Matched 'xyz' test");
            break;
        case /test/.test(str):
            console.log("• Matched 'test' test");
            break;
        case /ing/.test(str):
            console.log("• Matched 'ing' test");
            break;
        default:
            console.log("• Didn't match any test");
            break;
    }
}
  
test("testing");
test("xyz123");
test("foo");
test("fooing");
.as-console-wrapper {
    max-height: 100% !important;
}

That works because of the way JavaScript switch statements work, in particular two key aspects: First, that the cases are considered in source text order, and second that the selector expressions (the bits after the keyword case) are expressions that are evaluated as that case is evaluated (not constants as in some other languages). So since our test expression is true, the first case expression that results in true will be the one that gets used.

The reason I wouldn't recommend it in most situations is that it's cumbersome as well as being somewhat surprising (to people reading it later) compared to the equivalent if/else if/else:

function test(str) {
    if (/xyz/.test(str)) {
        console.log("• Matched 'xyz' test");
    } else if (/test/.test(str)) {
        console.log("• Matched 'test' test");
    } else if (/ing/.test(str)) {
        console.log("• Matched 'ing' test");
    } else {
        console.log("• Didn't match any test");
    }
}

Live Example:

function test(str) {
    console.log("Testing '" + str + "':");
    if (/xyz/.test(str)) {
        console.log("• Matched 'xyz' test");
    } else if (/test/.test(str)) {
        console.log("• Matched 'test' test");
    } else if (/ing/.test(str)) {
        console.log("• Matched 'ing' test");
    } else {
        console.log("• Didn't match any test");
    }
}
  
test("testing");
test("xyz123");
test("foo");
test("fooing");
.as-console-wrapper {
    max-height: 100% !important;
}

In both cases, the code does the same things in the same order, but unless you're well-versed in JavaScript arcana, the latter is clearer (arguably even if you are).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    "but I wouldn't recommend it in most situations." Why not? – Zebiano May 08 '23 at 16:25
  • 1
    @Zebiano - Thank you for asking me that, I was surprised to find I hadn't explained at all in the final paragraph. I've added an explanation (and generally updated the answer a bit). Happy coding! – T.J. Crowder May 08 '23 at 16:42
109

RegExp can be used on the input string with the match method too.

To ensure that we have a match in a case clause, we will test the original str value (that is provided to the switch statement) against the input property of a successful match.

input is a static property of regular expressions that contains the original input string.

When match fails it returns null. To avoid an exception error we use optional chaining operator (or the logical || conditional operator in legacy ES) before accessing the input property.

const str = 'XYZ test';

switch (str) {
  case str.match(/^xyz/)?.input:
    console.log("Matched a string that starts with 'xyz'");
    break;
  case str.match(/test/)?.input:
    console.log("Matched the 'test' substring");        
    break;
  default:
    console.log("Didn't match");
    break;
}

Another approach is to use the String() constructor to convert the resulting array that must have only 1 element (no capturing groups) and whole string must be captured with quantifiers (.*) to a string. In case of a failure the null object will become a 'null' string. That may seem less convenient.

const str = 'XYZ test';

switch (str.toLowerCase()) {
  case String(str.match(/^xyz.*/i)):
    console.log("Matched a string without case sensitivity");
    break;
  case String(str.match(/.*tes.*/)):
    console.log("Matched a string using a substring 'tes'");
    break;
}

Anyway, a more elegant solution is to use the test method instead of match, i.e. /^find-this-in/.test(str) with switch (true) which simply returns a boolean value and it's easier to match without case sensitivity.

const str = 'haystack';

switch (true) {
  case /^hay.*/i.test(str):
    console.log("Matched a string that starts with 'hay'");
    break;
}

However using if else else if statements in such scenarios would be readable too

Steven Pribilinskiy
  • 1,862
  • 1
  • 19
  • 21
  • 5
    pribilinsiky: you should probably mention that your third solution (using test()) requires you to have switch(true). – traday Jan 21 '15 at 02:10
35

Just use the location.host property

switch (location.host) {
    case "xxx.local":
        settings = ...
        break;
    case "xxx.dev.yyy.com":
        settings = ...
        break;
}
Sean Kinsey
  • 37,689
  • 7
  • 52
  • 71
20

Another option is to use input field of a regexp match result:

str = 'XYZ test';
switch (str) {
  case (str.match(/^xyz/) || {}).input:
    console.log("Matched a string that starts with 'xyz'");
    break;
  case (str.match(/test/) || {}).input:
    console.log("Matched the 'test' substring");        
    break;
  default:
    console.log("Didn't match");
    break;
}
Mitar
  • 6,756
  • 5
  • 54
  • 86
7
var token = 'spo';

switch(token){
    case ( (token.match(/spo/) )? token : undefined ) :
       console.log('MATCHED')    
    break;;
    default:
       console.log('NO MATCH')
    break;;
}


--> If the match is made the ternary expression returns the original token
----> The original token is evaluated by case

--> If the match is not made the ternary returns undefined
----> Case evaluates the token against undefined which hopefully your token is not.

The ternary test can be anything for instance in your case

( !!~ base_url_string.indexOf('xxx.dev.yyy.com') )? xxx.dev.yyy.com : undefined 

===========================================

(token.match(/spo/) )? token : undefined ) 

is a ternary expression.

The test in this case is token.match(/spo/) which states the match the string held in token against the regex expression /spo/ ( which is the literal string spo in this case ).

If the expression and the string match it results in true and returns token ( which is the string the switch statement is operating on ).

Obviously token === token so the switch statement is matched and the case evaluated

It is easier to understand if you look at it in layers and understand that the turnery test is evaluated "BEFORE" the switch statement so that the switch statement only sees the results of the test.

Tegra Detra
  • 24,551
  • 17
  • 53
  • 78
  • Your answer is confusing. Can you review and improve the example and the explanation? – falsarella Apr 15 '14 at 18:05
  • @falsarella I explained the part I imagined you had trouble understanding. I don`t think I can make a simpler example . If you have more questions or can be more specific with your difficulties I can help more. – Tegra Detra Apr 18 '14 at 15:38
  • Ok, now I got it. I was confused because it is obvious that `token.match(/spo/)` would match. – falsarella Apr 25 '14 at 22:28
3

It may be easier. Try to think like this:

  • first catch a string between regular characters
  • after that find "case"

:

// 'www.dev.yyy.com'
// 'xxx.foo.pl'

var url = "xxx.foo.pl";

switch (url.match(/\..*.\./)[0]){
   case ".dev.yyy." :
          console.log("xxx.dev.yyy.com");break;

   case ".some.":
          console.log("xxx.foo.pl");break;
} //end switch
Geery.S
  • 121
  • 2
  • 10
1

Might be too late and all, but I liked this in case assignment :)

function extractParameters(args) {
    function getCase(arg, key) {
        return arg.match(new RegExp(`${key}=(.*)`)) || {};
    }

    args.forEach((arg) => {
        console.log("arg: " + arg);
        let match;
        switch (arg) {
            case (match = getCase(arg, "--user")).input:
            case (match = getCase(arg, "-u")).input:
                userName = match[1];
                break;

            case (match = getCase(arg, "--password")).input:
            case (match = getCase(arg, "-p")).input:
                password = match[1];
                break;

            case (match = getCase(arg, "--branch")).input:
            case (match = getCase(arg, "-b")).input:
                branch = match[1];
                break;
        }
    });
};

you could event take it further, and pass a list of option and handle the regex with |

TacB0sS
  • 10,106
  • 12
  • 75
  • 118
  • 1
    I would also change `|| {}` to `|| [-1]` or similar for type safety. Also, why is `new RegExp` used, not just slashes? – Sergey Krasilnikov Jan 15 '19 at 10:40
  • didn't really took the time to refine it.. the moment it worked I just continued..... I feel ashamed now. – TacB0sS Jan 15 '19 at 19:27
  • Don't panic, that was just my nit-picking;) In fact I'm not even sure I'm right, I tried to learn smth new. – Sergey Krasilnikov Jan 18 '19 at 19:56
  • No... you are correct... I definitely could have generify and prettify.. I will when I get to that code again.. will be soon enough I hope :) – TacB0sS Jan 18 '19 at 19:58
0

Self-contained version that increases job security:

switch((s.match(r)||[null])[0])

function identifyCountry(hostname,only_gov=false){
    const exceptionRe = /^(?:uk|ac|eu)$/ ; //https://en.wikipedia.org/wiki/Country_code_top-level_domain#ASCII_ccTLDs_not_in_ISO_3166-1
    const h = hostname.split('.');
    const len = h.length;
    const tld = h[len-1];
    const sld = len >= 2 ? h[len-2] : null;

    if( tld.length == 2 ) {
        if( only_gov && sld != 'gov' ) return null;
        switch(  ( tld.match(exceptionRe) || [null] )[0]  ) {
         case 'uk':
            //Britain owns+uses this one
            return 'gb';
         case 'ac':
            //Ascension Island is part of the British Overseas territory
            //"Saint Helena, Ascension and Tristan da Cunha"
            return 'sh';
         case null:
            //2-letter TLD *not* in the exception list;
            //it's a valid ccTLD corresponding to its country
            return tld;
         default:
            //2-letter TLD *in* the exception list (e.g.: .eu);
            //it's not a valid ccTLD and we don't know the country
            return null;
        }
    } else if( tld == 'gov' ) {
        //AMERICAAA
        return 'us';
    } else {
        return null;
    }
}
<p>Click the following domains:</p>
<ul onclick="console.log(`${identifyCountry(event.target.textContent)} <= ${event.target.textContent}`);">
    <li>example.com</li>
    <li>example.co.uk</li>
    <li>example.eu</li>
    <li>example.ca</li>
    <li>example.ac</li>
    <li>example.gov</li>
</ul>

Honestly, though, you could just do something like

function switchableMatch(s,r){
    //returns the FIRST match of r on s; otherwise, null
    const m = s.match(r);
    if(m) return m[0];
    else return null;
}

and then later switch(switchableMatch(s,r)){…}

0

You could also make use of the default case like this:

    switch (name) {
        case 't':
            return filter.getType();
        case 'c':
            return (filter.getCategory());
        default:
            if (name.startsWith('f-')) {
                return filter.getFeatures({type: name})
            }
    }
Omar Dulaimi
  • 846
  • 10
  • 30
0

If you need to use a regular expression, create an object with regular expressions and a conditional response for the switch case

let test = (str) => {
    let obj = {
        'foo':'bar',
        '\/albums?':'photo'
    };
    for(let prop in obj){
        if(new RegExp(prop).test(str))return obj[prop]
    };
};

switch(test(location.href)){
    case 'bar':
        console.log('url has bar')
    break;
}
killovv
  • 51
  • 3