JSONC-parser for Javascript #4
M68HC060
started this conversation in
Show and tell
Replies: 1 comment
-
|
Here's mine. I'm still polishing it, but it supports a variety of “JSON-ish” formats like line-delimited JSON, JSONP, and those annoying JSON files with Function source/**
* Parse untrusted JSON data.
*
* The function supports “streamable” JSON, JSON with comments, and JSONP.
* Padded JSON input will be returned as a function that invokes the named
* callback, enumerated with the parsed JSONP data.
*
* @param {String|Iterable} input - JSON source to parse
* @param {String} [mimeType="application/json"] - MIME content-type describing the input
* @param {Object} [context=globalThis] - Object on which to lookup named JSONP callbacks
* @param {Boolean} [quiet=false] - Suppress warning messages printed to console
* @return {Object|Array|String|Number|Boolean|Function}
*/
export function parseJSON(input, mimeType = "application/json", context = globalThis, quiet = false){
if("string" !== typeof input)
input = Symbol.iterator in input ? [...input].join("") : `${input}`;
// Strip byte-order mark, if present
if(input.startsWith("\uFEFF"))
input = input.slice(1);
// Normalise MIME content-types
mimeType = `${mimeType}`.toLowerCase().split(";")[0]?.trim();
if(mimeType.length && !mimeType.includes("/"))
mimeType = "application/" + mimeType;
// Handle weird, non-standard JSON-ish formats
switch(mimeType){
// RFC 7464 JSON sequences
case "application/json-seq": {
const results = [];
for(let record of input.split("\x1E"))
if(record = record.trim())
results.push(parseJSON(record, "application/json", context));
return results;
}
// Line-delimited JSON, with blank lines ignored
case "application/jsonl":
case "application/jsonlines":
case "application/json-lines":
case "application/ndjson":
case "application/x-ndjson": {
const results = [];
let number = 0;
for(let line of input.split("\n")) if(++number && (line = line.trim()))
try{ results.push(parseJSON(line, "application/json", context)); }
catch(e){
const error = new SyntaxError("Invalid JSON record", {cause: e});
error.lineNumber = number;
throw error;
}
return results;
}
// Check if misidentified JSONC begins with a modeline
case "application/json": {
if(!/^\s*(?:\/\*\s*-\*-\s*jsonc\s*-\*-\s*\*\/|\/\/\s*-\*-\s*jsonc\s*-\*-)/i.test(input))
break;
// Fall-through
}
// JSON with comments
case "application/jsonc":
const str = /"(?:[^"\\]|\\.)*"/gm;
input = input
// Assumption: C0 control codes won't occur in JSONC source
.replace(str, str => str
.replaceAll("//", "\x01")
.replaceAll("/*", "\x02")
.replaceAll("*/", "\x03"))
.replace(/\/\*(?:[^*]|\*(?!\/))*\*\//gm, "")
.replace(/\/\/.*$/gm, "")
.replace(str, str => str
.replaceAll("\x01", "//")
.replaceAll("\x02", "/*")
.replaceAll("\x03", "*/")
.replaceAll("[", "\x01")
.replaceAll("]", "\x02")
.replaceAll("{", "\x03")
.replaceAll("}", "\x04"))
.replace(/,(\s*(?:\]|}))/g, "$1")
.replace(str, str => str
.replaceAll("\x01", "[")
.replaceAll("\x02", "]")
.replaceAll("\x03", "{")
.replaceAll("\x04", "}"));
}
// Deal with so-called “padded” JSON data
const jsonp = input.match(/^\s*([\w$]+)\s*\(\s*([^]*)\s*\)\s*;?\s*$/);
if(jsonp){
const callback = jsonp[1];
const json = parseJSON(jsonp[2]);
if("function" !== typeof context?.[callback])
quiet || console.warn("Named callback is not a function", context[callback]);
const obj = {[callback]: () => context[callback](json)};
return Object.assign(obj[callback], json, {[Symbol.toStringTag]: callback});
}
// Strip XSSI inhibitor string, if present
const xssi = input.match(/^\)\]\}',?\n|^for ?\(;;\);?|^while ?\(1\);?/);
if(xssi){
quiet || console.warn("Stripped '%s' from beginning of JSON source", xssi[0]);
input = input.slice(xssi[0].length);
}
return JSON.parse(input);
}Pathologic input example// -*- jsonc -*-
{
"foo": "bar",
"Foo /* Block comment */ Bar": "Foo /* Block comment */ Bar", /* Block comment */
"Line // Comment": "Line // Comment", // Line comment
"Trailing, ] commas, }": false,
"object": {
"one":/* Embedded comment */1,
"two": 2,
},
}Parsed result{
foo: 'bar',
'Foo /* Block comment */ Bar': 'Foo /* Block comment */ Bar',
'Line // Comment': 'Line // Comment',
'Trailing, ] commas, }': false,
object: {
one: 1,
two: 2
}
} |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
This is my simple RegEx solution for parsing JSONC in Javascript:
As the modified json text is parsed by JSON.parse, white-spaces are not removed.
It works with an ES5 compliant parser (tested on duktape - see duktape.org).
It's designed so it'll take both and based input files.
The only drawback from using this, is that any errors reported by JSON.parse, will not give you accurate positions (eg character-positions will always be incorrect. Line numbers may be ruined when using multi-line comments, but if you use only C++ style comments, line numbers will not be affected).
Enjoy.
Beta Was this translation helpful? Give feedback.
All reactions