Reputation: 2923
What is the easiest method to parse "relaxed" JSON but avoid evil eval
?
The following throws an error:
JSON.parse("{muh: 2}");
since proper JSON should have keys quoted: {"muh": 2}
My use case is a simple test interface I use to write JSON commands to my node server. So far I simply used eval
as it's just a test application anyway. However, using JSHint on the whole project keeps bugging me about that eval
. So I'd like a safe alternative that still allows relaxed syntax for keys.
PS: I don't want to write a parser myself just for the sake of the test application :-)
Upvotes: 80
Views: 46849
Reputation: 3183
This is my simple solution that works with nested object AND semi colons in values:
const finalJson = relaxedJson
.replace(/([{,])\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:/g, "$1\"$2\":");
Upvotes: 0
Reputation: 2094
If you are writing NodeJS code, you can also use the node:vm
module to create a safer environment than that of eval
to parse a relaxed JSON. Although the vm
environment can run arbitrary code, it will be tighter, it's a pure V8 sandbox, without things such as require
or process
.
const vm = require('vm'); // or node:vm
const badJson = '{muh: 2}';
try {
const parsedJson = new vm.Script(`x=${badJson}`).runInNewContext(
{ console: undefined }, // nuke console inside the vm
{ timeout: 1000, displayErrors: true }
);
if (typeof parsedJson !== 'object') { // in case you're expecting an object/array
throw new Error(`Invalid JSON=${badJson}, parsed as: ${parsedJson}`);
}
console.log(parsedJson);
} catch (err) {
throw new Error(`Could not parse JSON: ${err}`);
}
You can use vm2
instead, a module that promises more security and does the de same: https://github.com/patriksimek/vm2
Upvotes: 2
Reputation: 1027
[EDIT: This solution only serves for pretty simple objects and arrays, but does not do well with more complicated scenarios like nested objects. I recommend using something like jsonrepair to handle more interesting cases.]
I've modified Arnaud's solution slightly to handle periods in the keys, colons in the key values and arbitrary whitespace (although it doesn't deal with JSON object key values):
var badJson = `{
firstKey: "http://fdskljflksf",
second.Key: true, thirdKey:
5, fourthKey: "hello"
}`;
/*
\s*
any amount of whitespace
(['"])?
group 1: optional quotation
([a-z0-9A-Z_\.]+)
group 2: at least one value key character
(['"])?
group 3: optional quotation
\s*
any amount of whitespace
:
a colon literal
([^,\}]+)
group 4: at least one character of the key value (strings assumed to be quoted), ends on the following comma or closing brace
(,)?
group 5: optional comma
*/
var correctJson = badJson.replace(/\s*(['"])?([a-z0-9A-Z_\.]+)(['"])?\s*:([^,\}]+)(,)?/g, '"$2": $4$5');
JSON.parse(correctJson);
Upvotes: 2
Reputation: 29109
You can also use Flamenco's really-relaxed-json (https://www.npmjs.com/package/really-relaxed-json) that goes a step further and allows no commas, dangling commas, comments, multiline strings, etc.
Here's the specification http://www.relaxedjson.org
And some online parsers:
http://www.relaxedjson.org/docs/converter.html
Preloaded with the 'bad json'
{one : "1:1", two : { three: '3:3' }}
Preloaded with even 'worse json' (no commas)
{one : '1:1' two : { three: '3:3' }}
Preloaded with 'terrible json' (no commas, no quotes, and escaped colons)
{one : 1\:1 two : {three : 3\:3}}
Upvotes: 2
Reputation: 10878
You already know this, since you referred me here, but I figure it might be good to document it here:
I'd long had the same desire to be able to write "relaxed" JSON that was still valid JS, so I took Douglas Crockford's eval-free json_parse.js and extended it to support ES5 features:
https://github.com/aseemk/json5
This module is available on npm and can be used as a drop-in replacement for the native JSON.parse()
method. (Its stringify()
outputs regular JSON.)
Hope this helps!
Upvotes: 34
Reputation: 27330
This is what I ended up having to do. I extended @ArnaudWeil's answer and added support for having :
appear in the values:
var badJSON = '{one : "1:1", two : { three: \'3:3\' }}';
var fixedJSON = badJSON
// Replace ":" with "@colon@" if it's between double-quotes
.replace(/:\s*"([^"]*)"/g, function(match, p1) {
return ': "' + p1.replace(/:/g, '@colon@') + '"';
})
// Replace ":" with "@colon@" if it's between single-quotes
.replace(/:\s*'([^']*)'/g, function(match, p1) {
return ': "' + p1.replace(/:/g, '@colon@') + '"';
})
// Add double-quotes around any tokens before the remaining ":"
.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?\s*:/g, '"$2": ')
// Turn "@colon@" back into ":"
.replace(/@colon@/g, ':')
;
console.log('Before: ' + badJSON);
console.log('After: ' + fixedJSON);
console.log(JSON.parse(fixedJSON));
It produces this output:
Before: {one : "1:1", two : { three: '3:3' }}
After: {"one": "1:1", "two": { "three": "3:3" }}
{
"one": "1:1",
"two": {
"three": "3:3"
}
}
Upvotes: 20
Reputation: 2492
You could sanitize the JSON using a regular expression replace:
var badJson = "{muh: 2}";
var correctJson = badJson.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": ');
JSON.parse(correctJson);
Upvotes: 45
Reputation: 104780
If you can't quote keys when writing the string, you can insert quotes before using JSON.parse-
var s= "{muh: 2,mah:3,moh:4}";
s= s.replace(/([a-z][^:]*)(?=\s*:)/g, '"$1"');
var o= JSON.parse(s);
/* returned value:[object Object] */
JSON.stringify(o)
/* returned value: (String){
"muh":2, "mah":3, "moh":4
}
Upvotes: 7