Javascript split by spaces but not those in quotes

The goal is to split a string at the spaces but not split the text data that is in quotes or separate that from the adjacent text.

The input is effectively a string that contains a list of value pairs. If the value value contains a space it is enclosed in quotes. I need a function that returns an array of value-pair elements as per the example below:

Example Input:

'a:0 b:1 moo:"foo bar" c:2'

Expected result:

a:0,b:1,moo:foo bar,c:2 (An array of length 4)

I have checked through a load of other questions but none of them (I found) seem to cope with my issue. Most seem to split at the space within the quotes or they split the 'moo:' and 'foo bar' into separate parts.

Any assistance would be greatly appreciated, Craig


Solution 1:

You can use this regex for split:

var s = 'a:0 b:1 moo:"foo bar" c:2';

var m = s.split(/ +(?=(?:(?:[^"]*"){2})*[^"]*$)/g);
//=> [a:0, b:1, moo:"foo bar", c:2]

RegEx Demo

It splits on spaces only if it is outside quotes by using a positive lookahead that makes sure there are even number of quotes after a space.

Solution 2:

You could approach it slightly differently and use a Regular Expression to split where spaces are followed by word characters and a colon (rather than a space that's not in a quoted part):

var str = 'a:0 b:1 moo:"foo bar" c:2',
    arr = str.split(/ +(?=[\w]+\:)/g);
/* [a:0, b:1, moo:"foo bar", c:2] */

Demo jsFiddle

What's this Regex doing?
It looks for a literal match on the space character, then uses a Positive Lookahead to assert that the next part can be matched:
[\w]+ = match any word character [a-zA-Z0-9_] between one and unlimited times.
\: = match the : character once (backslash escaped).
g = global modifier - don't return on first match.

Demo Regex101 (with explanation)

Solution 3:

Any special reason it has to be a regexp?

var str = 'a:0 b:1 moo:"foo bar" c:2';

var parts = [];
var currentPart = "";
var isInQuotes= false;

for (var i = 0; i < str.length, i++) {
  var char = str.charAt(i);
  if (char === " " && !isInQuotes) {
    parts.push(currentPart);
    currentPart = "";
  } else {
    currentPart += char;
  }
  if (char === '"') {
    isInQuotes = !isInQuotes;
  }
}

if (currentPart) parts.push(currentPart);