abalter
abalter

Reputation: 10383

How to parse a string of key/value pairs in JavasScript, with pairs separated by commas?

I would like to be able to parse a sting such as

'a=1, b="two", c=[1,"two", {"a":1}]'

Into a JSON string or Javascript object such as

{ 
  "a": 1, 
  "b": "two", 
  "c": [1, "two", {"a":1}] 
}

This question is similar to Parse string having key=value pairs as JSON and Javascript Parsing Key Value String to JSON, but having the KV pairs separated by commas is challenging because the values themselves can have commas.

Suggestions? Existing packages?

Upvotes: 2

Views: 2118

Answers (3)

Christopher
Christopher

Reputation: 193

Dec 2024

some new tools have come around ;)

This works perfectly with my command line tool I am using to parse an unlimited amount of env variables passed to the bash script.

Example String input:

const str = options.env as string // TEST=app-test,ONE=app,IT=onemore
const env = str.split(",").reduce((acc: { [key: string]: string }, curr) => {
  const [key, value] = curr.split("=")
  acc[key.trim()] = value.trim().replace(/['"]/g, "")
  return acc
}, {})

console.log(env)

Example output:

{ TEST: 'app-test', ONE: 'app', IT: 'onemore' }

Upvotes: 1

Nick
Nick

Reputation: 147146

You can use this regex to find all the key=value pairs in your string:

([a-z]\w*)=((?:[^"]|"[^"]+")+?)(?=,\s*[a-z]\w*=|$)

Regex demo on regex101

It looks for an identifier (I've assumed they are a letter followed by one or more word characters, you can adjust to suit), followed by an =, then a string of non-quote characters or quoted strings. A lookahead is then used to assert that this is followed by a comma, some space, and an identifier with =, or end-of-string.

Having split the line into key/value pairs, you can then construct JSON elements out of them, join all the elements together into a JSON string and parse it:

const str = 'a=1, b="two", c=[1,"two", {"a":1}]';

const regex = /([a-z]\w*)=((?:[^"]|"[^"]+")+?)(?=,\s*[a-z]\w*=|$)/g

let m;
let els = [];

while ((m = regex.exec(str)) !== null) {
  els.push(`"${m[1]}": ${m[2]}`);
}

const json = '{' + els.join(',') + '}';

const obj = JSON.parse(json);
console.log(obj);

Upvotes: 4

Bojoer
Bojoer

Reputation: 948

U could accomplish this using regular expressions to be sure that the comma's used to separate key/value pairs and comma's used inside values are recognized.

But this also is tricky if for example your string is not normalized and maybe contains multiple whitespaces or if there is also a match inside a value that is equal to , key=

But better would be to normalize your string and make sure that the key/value separator is never used inside the value itself. if you need to use a comma to separate key/values then it would be best replacing all comma's inside all values before they are added to the string and after the string is parsed to JSON You should replace them back to comma's in each JSON value.

So actually this is very bad practice and what would be the reason to use this instead of changing the cause of your issue and that is using a unique separator.

Working example but again this only works if there is no = character present in the values in the original string but that is also true when using regex.

const input = 'a=1, b="two", c=[1,"two", {"a":1}]';
let arr = input.split(',');
let jsonString = '{';
arr.forEach(el => {
  if (el.indexOf('=') > -1) {
    const keyVal = el.split('=');
    jsonString += `"${keyVal[0].replace(' ', '')}": ${keyVal[1]},`;
  } else {
    jsonString += `${el},`;
  }
});
jsonString = jsonString.replace(/.$/,"}");
const jsonObject = JSON.parse(jsonString);

console.log(JSON.stringify(jsonObject));

Upvotes: 1

Related Questions