Switch statement for string matching in JavaScript
Asked Answered
S

10

229

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/"
    };
}
Sofar answered 24/5, 2010 at 11:33 Comment(0)
P
403

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

Parvis answered 24/5, 2010 at 11:35 Comment(2)
"but I wouldn't recommend it in most situations." Why not?Emarginate
@Emarginate - 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!Parvis
P
116

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

Poinciana answered 18/9, 2013 at 19:53 Comment(1)
pribilinsiky: you should probably mention that your third solution (using test()) requires you to have switch(true).Refutation
P
35

Just use the location.host property

switch (location.host) {
    case "xxx.local":
        settings = ...
        break;
    case "xxx.dev.yyy.com":
        settings = ...
        break;
}
Phenacetin answered 24/5, 2010 at 11:38 Comment(2)
Thanks, +1 as this is what I should be doing reallySofar
You have to take care about the variable type you pass to the switch statement. It must be a string. To be sure you can do switch ("" + location.host).Unlisted
H
21

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;
}
Herbherbaceous answered 25/7, 2014 at 1:36 Comment(1)
nice one. In this case any array property is also can be used for test, e.g. .length:Poinciana
A
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.

Airframe answered 28/2, 2014 at 15:23 Comment(3)
Your answer is confusing. Can you review and improve the example and the explanation?Queenstown
@Queenstown 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.Airframe
Ok, now I got it. I was confused because it is obvious that token.match(/spo/) would match.Queenstown
V
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
Vandervelde answered 18/6, 2017 at 10:4 Comment(1)
Upvoted. But note: TypeError: url.match(...) is nullBueschel
S
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 |

Sibling answered 15/1, 2018 at 12:3 Comment(4)
I would also change || {} to || [-1] or similar for type safety. Also, why is new RegExp used, not just slashes?Valenta
didn't really took the time to refine it.. the moment it worked I just continued..... I feel ashamed now.Sibling
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.Valenta
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 :)Sibling
I
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)){…}

Initial answered 26/9, 2020 at 23:9 Comment(0)
L
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})
            }
    }
Lattermost answered 24/11, 2020 at 12:59 Comment(0)
T
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;
}
Trude answered 22/1, 2023 at 16:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.