JSON
JSON is a not a thing, it is a transport file format, held in memory as a String
In JavaScript it is parsed using JSON.parse and the result is an Object.
To refer to an object as a json_thing
is incorrect (yes its a "thing") but it's not a string or blob formatted to as JSON.
The only thing that makes an object unique when created from a JSON string is that you are guaranteed not to have cyclic references.
General notes.
There is a lot of code noise in your code. Code noise is anything that does not contribute to the algorithm process
You modify the array in place, but you copy objects. Best if you treat arrays and objects the same, either in place or as a copy. The rewrites give examples of both
Call stack overflow
JavaScript uses a call stack (as do most languages) unfortunately almost all JavaScript implementations do not recognize tail calls (even though its part of the ECMAScript6 standard) meaning that recursion is dangerous.
There is no way to know how deep the call stack is, nor how deep you must go to complete the task. Always be wary of this when creating recursive code.
The rewrites gives an example of a non recursive solution that can process a much deeper object than a recursive function can.
Code style
Declare variable at top of function as this is where variables declared as var are created. Function declaration (eg function name(){}
) are also hoisted to the top of their scope in JavaScript.
Note that let and const are block scope {/*block*/}
and are not hoisted to the top of their scope. You can not use them before they are declared.
If you don't need the index use a for...of of loop rather than a for loop.
Note Using callback iterators can add overhead to processing and are generals slower than for loops.
Avoid single use variables unless they help keep the code clean.
JavaScript logical operators always execute in the same order (unlike C/C++) and can thus be used to reduce code noise via short circuit syntax style. Eg if (foo) { foo = 0 }
can be foo && (foo = 0);
Note that the ()
around the second (3rd etc...) logical clause is required if there is an assignment operator within a short circuit expression.
Note you can not short circuit flow control eg foo && return
is illegal. Same for break
, continue
, if
, for
etc...
As the order of logical and grouped expressions / statements is always the same you can optimize them using most likely outcome. eg testing z !== null && typeof z === "object"
can be optimized as typeof z === "object" && z !== null
as it is far more likely that z is not an object than it being null
Use the short form/style when you can. Eg if (false === isSortable(obj)) {
can be if (!isSortable(obj)) {
Function arguments are function scoped and writeable (almost identical to var
). Reuse them if it helps reduce code noise.
Object.keys, Object.entries Object.values works on Array, Object, and String thus you do not need to treat object and array separately.
Array is a typeof
"object"
. The Array.isArray
in isSortable
is redundant.
Note that typeof is a JavaScript operator and not a function. typeof(z)
is the same as typeof z
the grouping operator has no effect in this case
You can use Object.fromEntries to create an object from an array of key, name pairs. Object.fromEntries([["A", 1], ["B", 2]])
will define an object {A:1, B:1}
Rewrite
Rewrite removes the code noise and redundant code.
I have provided three versions.
uses recursion and creates a copy of objects that are sorted. This rewrite is the only one the matches you function`s behavior
uses a stack and sorts object keys in place. Only arrays and object are pushed to the stack.
Same as 2 but checks for cyclic references and as such is the only rewrite safe to use.
NOTE ONLY use the first two on objects that do not have cyclic references. The recursive example will throw an error very quickly, the second will need to use up memory before it has a problem (this will lock the page as it chews up memory), and then depending on the engine could crash the page/thread or throw an error.
Recursive
function sortKeysDeeply(obj) {
if (isObj(obj)) {
!Array.isArray(obj) && (obj = sortObjectKeys(obj));
for (const [key, val] of Object.entries(sorted)) {
isObj(val) && (obj[key] = sortKeysDeeply(val));
}
}
return obj;
}
Using a stack
function sortKeysDeeply(obj) {
const stack = [];
if (isObj(obj)) {
stack.push(obj);
while (stack.length) {
let o = stack.pop();
o = !Array.isArray(o) && sortInPlaceObjectKeys(o);
for (const val of Object.values(o)) { isObj(val) && stack.push(val) }
}
}
return obj;
}
Cyclic safe stack
This uses a WeakSet to track which object references have been encountered.
A week set only holds the generated hash rather than the object and thus uses a little less memory than Set and reduces the GC workload.
You can use this in the recursive code as well.
function sortKeysDeeply(obj) {
const stack = [], checked = new WeakSet();
if (isObj(obj)) {
stack.push(obj);
while (stack.length) {
let o = stack.pop();
checked.add(o);
o = !Array.isArray(o) && sortInPlaceObjectKeys(o);
for (const val of Object.values(o)) {
isObj(val) && !checked.has(val) && stack.push(val);
}
}
}
return obj;
}
Helper functions
Function required by above rewrites.
const strSorter = (a, b) => a > b ? 1 : a < b ? -1 : 0;
const isObj = obj => 'object' === typeof(obj) && null !== obj;
const sortObjectKeys = obj =>
Object.fromEntries(Object.entries(obj).sort((a, b) => strSorter(a[0], b[0])));
const sortInPlaceObjectKeys = obj => {
const sorted = Object.entries(obj).sort((a, b) => strSorter(a[0], b[0]));
for (const [key] of sorted) { delete obj[key] }
return Object.assign(obj, Object.fromEntries(sorted));
}
Array.sort()
can sortnull
if done so explicitly in acompareFunction
passed intosort()
. See MDN documentation. \$\endgroup\$false === _is_sortable(json_thing)
-> left-handed boolean comparison! ... 15 yr dormant PTSD nightmares returning! A former C programmer? Nostalgia? Seriously, just curious why. Has ES5 or beyond not fixed the need for this? The world wonders \$\endgroup\$