diff --git a/snippets/mongocompat/mongotypes.js b/snippets/mongocompat/mongotypes.js index 60350c1..76c84f4 100644 --- a/snippets/mongocompat/mongotypes.js +++ b/snippets/mongocompat/mongotypes.js @@ -226,7 +226,7 @@ Array.shuffle = function(arr) { return arr; }; -Array.tojson = function(a, indent, nolint, depth) { +Array.tojson = function(a, indent, nolint, depth, sortedKeys) { if (!Array.isArray(a)) { throw new Error("The first argument to Array.tojson must be an array"); } @@ -256,7 +256,7 @@ Array.tojson = function(a, indent, nolint, depth) { indent += "\t"; for (var i = 0; i < a.length; i++) { - s += indent + tojson(a[i], indent, nolint, depth + 1); + s += indent + tojson(a[i], indent, nolint, depth + 1, sortedKeys); if (i < a.length - 1) { s += "," + elementSeparator; } @@ -719,7 +719,7 @@ tojsononeline = function(x) { return tojson(x, " ", true); }; -tojson = function(x, indent, nolint, depth) { +tojson = function(x, indent, nolint, depth, sortKeys) { if (x === null) return "null"; @@ -740,7 +740,7 @@ tojson = function(x, indent, nolint, depth) { case "boolean": return "" + x; case "object": { - var s = tojsonObject(x, indent, nolint, depth); + var s = tojsonObject(x, indent, nolint, depth, sortKeys); if ((nolint == null || nolint == true) && s.length < 80 && (indent == null || indent.length == 0)) { s = s.replace(/[\t\r\n]+/gm, " "); @@ -757,10 +757,13 @@ tojson = function(x, indent, nolint, depth) { }; tojson.MAX_DEPTH = 100; -tojsonObject = function(x, indent, nolint, depth) { +tojsonObject = function(x, indent, nolint, depth, sortKeys) { if (typeof depth !== 'number') { depth = 0; } + if (typeof sortKeys !== 'boolean') { + sortKeys = false; + } var lineEnding = nolint ? " " : "\n"; var tabSpace = nolint ? "" : "\t"; if (typeof x !== "object") { @@ -771,12 +774,12 @@ tojsonObject = function(x, indent, nolint, depth) { indent = ""; if (typeof (x.tojson) == "function" && x.tojson != tojson) { - return x.tojson(indent, nolint, depth); + return x.tojson(indent, nolint, depth, sortKeys); } if (x.constructor && typeof (x.constructor.tojson) == "function" && x.constructor.tojson != tojson) { - return x.constructor.tojson(x, indent, nolint, depth); + return x.constructor.tojson(x, indent, nolint, depth, sortKeys); } if (x instanceof Error) { @@ -802,8 +805,14 @@ tojsonObject = function(x, indent, nolint, depth) { var keys = x; if (typeof (x._simpleKeys) == "function") keys = x._simpleKeys(); - var fieldStrings = []; + var keyNames = []; for (var k in keys) { + keyNames.push(k); + } + if (sortKeys) keyNames.sort(); + + var fieldStrings = []; + for (var k of keyNames) { var val = x[k]; // skip internal DB types to avoid issues with interceptors @@ -812,7 +821,7 @@ tojsonObject = function(x, indent, nolint, depth) { if (typeof DBCollection != 'undefined' && val == DBCollection.prototype) continue; - fieldStrings.push(indent + "\"" + k + "\" : " + tojson(val, indent, nolint, depth + 1)); + fieldStrings.push(indent + "\"" + k + "\" : " + tojson(val, indent, nolint, depth + 1, sortKeys)); } if (fieldStrings.length > 0) { diff --git a/snippets/mongocompat/test.js b/snippets/mongocompat/test.js index f1fa715..5710255 100644 --- a/snippets/mongocompat/test.js +++ b/snippets/mongocompat/test.js @@ -131,3 +131,33 @@ try { } assert.strictEqual(typeof tojsonObject({ key: "value" }), 'string'); assert.strictEqual(typeof tojsonObject([1, 2, 3]), 'string'); + +// Test sortedkey parameter +const unsortedObj = { z: 1, a: 2, m: 3 }; +const sortedJson = tojson(unsortedObj, "", true, 0, true); +const unsortedJson = tojson(unsortedObj, "", true, 0, false); +const defaultJson = tojson(unsortedObj); +assert(sortedJson.indexOf('"a"') < sortedJson.indexOf('"m"'), 'sortedJson should be sorted alphabetically'); +assert(sortedJson.indexOf('"m"') < sortedJson.indexOf('"z"'), 'sortedJson should be sorted alphabetically'); +assert(unsortedJson.indexOf('"z"') < unsortedJson.indexOf('"a"'), 'unsortedJson should not be sorted alphabetically'); +assert(defaultJson.indexOf('"z"') < defaultJson.indexOf('"a"'), 'tojson without sortedkey should not sort keys'); +const nestedObj = { b: { y: 1, x: 2 }, a: { z: 1, a: 2 } }; +const sortedNestedJson = tojson(nestedObj, "", true, 0, true); +assert(sortedNestedJson.indexOf('"a"') < sortedNestedJson.indexOf('"b"'), 'sortedkey=true should sort top-level keys'); +assert(sortedNestedJson.indexOf('"a" :') < sortedNestedJson.indexOf('"z" :'), 'sortedkey=true should sort nested keys'); +const objWithBson = { + c: NumberLong(123), + b: ObjectId('0123456789abcdef01234567'), + a: NumberDecimal("1.1") +}; +const sortedBsonJson = tojson(objWithBson, "", true, 0, true); +assert(sortedBsonJson.indexOf('"a"') < sortedBsonJson.indexOf('"b"'), 'sortedkey=true should sort keys with BSON types'); +assert(sortedBsonJson.indexOf('"b"') < sortedBsonJson.indexOf('"c"'), 'sortedkey=true should sort keys with BSON types'); +const arrayWithObjects = [{ z: 1, a: 2 }, { y: 3, b: 4 }]; +const sortedArrayJson = Array.tojson(arrayWithObjects, "", true, 0, true); +const unsortedArrayJson = Array.tojson(arrayWithObjects, "", true, 0, false); +const defaultArrayJson = Array.tojson(arrayWithObjects, "", true, 0); +assert(sortedArrayJson.indexOf('"a"') < sortedArrayJson.indexOf('"z"'), 'Array.tojson with sortedKeys=true should sort object keys in array elements'); +assert(sortedArrayJson.indexOf('"b"') < sortedArrayJson.indexOf('"y"'), 'Array.tojson with sortedKeys=true should sort object keys in array elements'); +assert(unsortedArrayJson.indexOf('"z"') < unsortedArrayJson.indexOf('"a"'), 'Array.tojson with sortedKeys=false should not sort keys'); +assert(defaultArrayJson.indexOf('"z"') < defaultArrayJson.indexOf('"a"'), 'Array.tojson without sortedKeys should not sort keys');