Switch statement for string matching in JavaScript

JavascriptRegexSwitch Statement

Javascript Problem Overview


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/"
	};
}

Javascript Solutions


Solution 1 - Javascript

You can't do it in a switch unless you're doing full string matching; that's doing substring matching. (This isn't quite true, as Sean points out in the comments. See note at the end.)

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.


Update: Okay, so technically you can use a switch for substring matching, but I wouldn't recommend it in most situations. Here's how (live example):

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

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.

Solution 2 - Javascript

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.*/.test(str):
    console.log("Matched a string that starts with 'hay'");
    break;
}

Solution 3 - Javascript

Just use the location.host property

switch (location.host) {
    case "xxx.local":
        settings = ...
        break;
    case "xxx.dev.yyy.com":
        settings = ...
        break;
}

Solution 4 - Javascript

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;
}

Solution 5 - Javascript

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.

Solution 6 - Javascript

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

Solution 7 - Javascript

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 |

Solution 8 - Javascript

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)){…}

Solution 9 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDr. FrankensteinView Question on Stackoverflow
Solution 1 - JavascriptT.J. CrowderView Answer on Stackoverflow
Solution 2 - JavascriptSteven PribilinskiyView Answer on Stackoverflow
Solution 3 - JavascriptSean KinseyView Answer on Stackoverflow
Solution 4 - JavascriptMitarView Answer on Stackoverflow
Solution 5 - JavascriptJames AndinoView Answer on Stackoverflow
Solution 6 - JavascriptGeery.SView Answer on Stackoverflow
Solution 7 - JavascriptTacB0sSView Answer on Stackoverflow
Solution 8 - JavascriptJamesTheAwesomeDudeView Answer on Stackoverflow
Solution 9 - JavascriptOmar DulaimiView Answer on Stackoverflow