axkibe
axkibe

Reputation: 2923

Parsing "relaxed" JSON without eval

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

Answers (8)

Herobrine
Herobrine

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

ojosilva
ojosilva

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

therightstuff
therightstuff

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

Steven Spungin
Steven Spungin

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

Bad JSON

Preloaded with even 'worse json' (no commas)

{one : '1:1' two : { three: '3:3' }}

Worse JSON

Preloaded with 'terrible json' (no commas, no quotes, and escaped colons)

{one : 1\:1 two : {three : 3\:3}}

Terrible JSON

Upvotes: 2

Aseem Kishore
Aseem Kishore

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

Malvineous
Malvineous

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

Arnaud Weil
Arnaud Weil

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

kennebec
kennebec

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

Related Questions