/**
* @namespace utils
*/
/**
* This is a function for use in <code>JSON.stringify(input, sortedObjectKeys, [number])</code>
* that ensures that object keys are always sorted in alphanumerical order.
*
* @name utils.sortedObjectKeys
* @function
* @params {*} _ - an irrelevant value, I have no idea why this value is even passed into a replacer function.
* @params {*} data - The data at the current depth/iteration in the JSON.stringify pass.
* @returns The original data if not an object, otherwise an object with its keys sorted alphanumerically.
*/
export function sortedObjectKeys(_, data) {
// Ignore primitives.
if (data === null) return null;
if (typeof data !== "object") return data;
// Also ignore iterables, which are type "object" but should not be sorted.
if (data.__proto__[Symbol.iterator]) return data;
// Then sort the object keys and yield a new object with that
// ordering enforced by key insertion.
return Object.fromEntries(Object.entries(data).sort());
}
/**
* Deep-copy an object (not used atm, but it might as well be here).
* @ignore
*/
export function copyFromSource(source, constructed = false) {
const target = {};
setDataFrom(source, target, constructed);
return target;
}
/**
* deep-assign one object to another.
* @ignore
*/
export function setDataFrom(source, target, constructed = true) {
inflate(source);
for (const [key, val] of Object.entries(source)) {
if (val !== null && typeof val === `object`) {
if (target[key] === undefined) {
target[key] = constructed ? new val.__proto__.constructor() : {};
}
setDataFrom(val, target[key]);
} else {
target[key] = val;
}
}
}
/**
* Inflate a pure, flat pathkey:string object to
* a regular nested object instead. Note that if
* an object has any sort of nesting, this function
* will return without modifying the object.
*
* @name utils.inflate
* @params {*} data - A flat JS object
* @ignore
*/
export function inflate(data) {
const entries = Object.entries(data);
// As a short circuit, we don't inflate anything that isn't
// a pure, flat object.
if (entries.some(([_, v]) => typeof v === `object`)) {
return data;
}
entries.forEach(([key, value]) => {
const path = key.split(`.`);
delete data[key];
let level = data;
while (path.length > 1) {
const term = path.shift();
if (!level[term]) level[term] = {};
level = level[term];
}
level[path[0]] = value;
});
return data;
}