Repair button is now capable of turning MongoDB documents into valid JSON
This commit is contained in:
parent
cec16b9de1
commit
3f182a1f04
|
@ -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`,
|
||||
|
|
109
src/js/util.js
109
src/js/util.js
|
@ -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,11 +275,16 @@ 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 {
|
||||
chars.push(c);
|
||||
i++;
|
||||
if (/[a-zA-Z_$]/.test(c)) {
|
||||
chars.push(parseMongoDataType());
|
||||
}
|
||||
else {
|
||||
chars.push(c);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 () {
|
||||
|
|
Loading…
Reference in New Issue