Repair button is now capable of turning MongoDB documents into valid JSON

This commit is contained in:
jos 2019-07-27 14:16:59 +02:00
parent cec16b9de1
commit 3f182a1f04
3 changed files with 118 additions and 20 deletions

View File

@ -6,6 +6,7 @@ https://github.com/josdejong/jsoneditor
- Implemented new mode `preview`, capable of working with large JSON documents
up to 500 MiB.
- Repair button is now capable of turning MongoDB documents into valid JSON.
- Fixed #730: in `code` mode, there was an initial undo action which clears
the content.
- Upgraded dependencies `vanilla-picker@2.9.1`, `mobius1-selectr@2.4.13`,

View File

@ -36,6 +36,8 @@ exports.parse = function parse(jsonString) {
* @returns {string} json
*/
exports.repair = function (jsString) {
// TODO: refactor this function, it's too large and complicated now
// escape all single and double quotes inside strings
var chars = [];
var i = 0;
@ -116,41 +118,49 @@ exports.repair = function (jsString) {
}
}
// parse single or double quoted string
/**
* parse single or double quoted string. Returns the parsed string
* @param {string} endQuote
* @return {string}
*/
function parseString(endQuote) {
chars.push('"');
var string = '';
string += '"';
i++;
var c = curr();
while (i < jsString.length && c !== endQuote) {
if (c === '"' && prev() !== '\\') {
// unescaped double quote, escape it
chars.push('\\"');
string += '\\"';
}
else if (controlChars.hasOwnProperty(c)) {
// replace unescaped control characters with escaped ones
chars.push(controlChars[c])
string += controlChars[c]
}
else if (c === '\\') {
// remove the escape character when followed by a single quote ', not needed
i++;
c = curr();
if (c !== '\'') {
chars.push('\\');
string += '\\';
}
chars.push(c);
string += c;
}
else {
// regular character
chars.push(c);
string += c;
}
i++;
c = curr();
}
if (c === endQuote) {
chars.push('"');
string += '"';
i++;
}
return string;
}
// parse an unquoted key
@ -167,13 +177,69 @@ exports.repair = function (jsString) {
}
if (specialValues.indexOf(key) === -1) {
chars.push('"' + key + '"');
return '"' + key + '"';
}
else {
chars.push(key);
return key;
}
}
function parseMongoDataType () {
var c = curr();
var value;
var dataType = '';
while (/[a-zA-Z_$]/.test(c)) {
dataType += c
i++;
c = curr();
}
if (dataType.length > 0 && c === '(') {
// This is an MongoDB data type like {"_id": ObjectId("123")}
i++;
c = curr();
if (c === '"') {
// a data type containing a string, like ISODate("2012-12-19T06:01:17.171Z")
value = parseString(c);
c = curr();
}
else {
// a data type containing a value, like 'NumberLong(2)'
value = '';
while(c !== ')' && c !== '') {
value += c;
i++;
c = curr();
}
}
if (c === ')') {
// skip the closing bracket at the end
i++;
// return the value (strip the data type object)
return value;
}
else {
// huh? that's unexpected. don't touch it
return dataType + '(' + value + c;
}
}
else {
// hm, no Mongo data type after all
return dataType;
}
}
function isSpecialWhiteSpace (c) {
return (
c === '\u00A0' ||
(c >= '\u2000' && c <= '\u200A') ||
c === '\u202F' ||
c === '\u205F' ||
c === '\u3000')
}
while(i < jsString.length) {
var c = curr();
@ -183,25 +249,25 @@ exports.repair = function (jsString) {
else if (c === '/' && next() === '/') {
skipComment();
}
else if (c === '\u00A0' || (c >= '\u2000' && c <= '\u200A') || c === '\u202F' || c === '\u205F' || c === '\u3000') {
else if (isSpecialWhiteSpace(c)) {
// special white spaces (like non breaking space)
chars.push(' ');
i++
}
else if (c === quote) {
parseString(quote);
chars.push(parseString(c));
}
else if (c === quoteDbl) {
parseString(quoteDbl);
chars.push(parseString(quoteDbl));
}
else if (c === graveAccent) {
parseString(acuteAccent);
chars.push(parseString(acuteAccent));
}
else if (c === quoteLeft) {
parseString(quoteRight);
chars.push(parseString(quoteRight));
}
else if (c === quoteDblLeft) {
parseString(quoteDblRight);
chars.push(parseString(quoteDblRight));
}
else if (c === ',' && [']', '}'].indexOf(nextNonWhiteSpace()) !== -1) {
// skip trailing commas
@ -209,13 +275,18 @@ exports.repair = function (jsString) {
}
else if (/[a-zA-Z_$]/.test(c) && ['{', ','].indexOf(lastNonWhitespace()) !== -1) {
// an unquoted object key (like a in '{a:2}')
parseKey();
chars.push(parseKey());
}
else {
if (/[a-zA-Z_$]/.test(c)) {
chars.push(parseMongoDataType());
}
else {
chars.push(c);
i++;
}
}
}
return chars.join('');
};

View File

@ -76,7 +76,6 @@ describe('util', function () {
assert.strictEqual(util.repair('\n/* foo\nbar */\ncallback_123 ({});\n\n'), '{}');
// non-matching
assert.strictEqual(util.repair('callback abc({});'), 'callback abc({});');
assert.strictEqual(util.repair('callback {}'), 'callback {}');
assert.strictEqual(util.repair('callback({}'), 'callback({}');
});
@ -93,6 +92,33 @@ describe('util', function () {
assert.strictEqual(util.repair('"{a:2,}"'), '"{a:2,}"');
});
it('should strip MongoDB data types', function () {
const mongoDocument = '{\n' +
' "_id" : ObjectId("123"),\n' +
' "isoDate" : ISODate("2012-12-19T06:01:17.171Z"),\n' +
' "regularNumber" : 67,\n' +
' "long" : NumberLong("2"),\n' +
' "long2" : NumberLong(2),\n' +
' "int" : NumberInt("3"),\n' +
' "int2" : NumberInt(3),\n' +
' "decimal" : NumberDecimal("4"),\n' +
' "decimal2" : NumberDecimal(4)\n' +
'}';
const expectedJson = '{\n' +
' "_id" : "123",\n' +
' "isoDate" : "2012-12-19T06:01:17.171Z",\n' +
' "regularNumber" : 67,\n' +
' "long" : "2",\n' +
' "long2" : 2,\n' +
' "int" : "3",\n' +
' "int2" : 3,\n' +
' "decimal" : "4",\n' +
' "decimal2" : 4\n' +
'}';
assert.strictEqual(util.repair(mongoDocument), expectedJson);
});
});
describe('jsonPath', function () {