cspell-lib
Version:
A library of useful functions used across various cspell tools.
125 lines • 4.92 kB
JavaScript
/* eslint-disable unicorn/no-null */
/**
* Walks a value and converts it into a JSON-serializable representation.
*
* This function recursively traverses complex structures and normalizes them
* so they can be safely passed to {@link JSON.stringify}. The following
* conversions are applied:
*
* - Primitive types (`string`, `number`, `boolean`, `undefined`) are returned as-is.
* - `null` is returned as `null`.
* - Objects implementing `toJSON()` are serialized using that method.
* - `RegExp` instances are converted to their string form (e.g. `/pattern/gi`).
* - `Map` instances become arrays of `[key, value]` entry pairs, with both keys
* and values processed through `walkToJSONObj`.
* - `Set` instances become arrays of their values, each processed through
* `walkToJSONObj`.
* - Boxed `String` objects are converted to primitive strings.
* - Arrays are mapped element-wise via `walkToJSONObj`.
* - Plain objects are converted to new objects whose property values are
* processed via `walkToJSONObj`.
* - `bigint` values are currently returned as-is (note: not natively supported
* by `JSON.stringify`).
* - Functions are converted to `undefined`.
*
* @param value - The value to transform into a JSON-serializable structure.
* @returns A JSON-serializable representation of the input value.
*/
export function walkToJSONObj(value) {
const mapVisited = new WeakMap();
function walk(value) {
switch (typeof value) {
case 'string':
case 'number':
case 'boolean':
case 'undefined': {
return value;
}
case 'object': {
return walkObj(value);
}
case 'bigint': {
return value; // return as is for now
}
case 'function': {
return undefined; // `[function ${value.name || 'anonymous'}]`;
}
default: {
return undefined;
}
}
}
/**
* Adds the value/result pair to the visited map.
* @param value - the original object
* @param result - the processed or to be processed result
* @returns result
*/
function rememberObj(value, result) {
mapVisited.set(value, result);
return result;
}
/**
* Adds an array to the visited map and processes its entries in place.
* @param value - the original object
* @param entries - the array entries, it will be modified in place.
* @param map - an optional mapping function to apply to each entry.
* @returns the processed array.
*/
function rememberArrayAndMapValuesInPlace(value, entries, map = (entry) => walk(entry)) {
const result = rememberObj(value, entries);
for (let i = 0; i < entries.length; i++) {
entries[i] = map(entries[i], i);
}
return result;
}
/**
* Walk an object.
* This function handles circular references.
*
* It does this by adding a placeholder to the visited map before processing the object.
* The placeholder is updated in place with the final result after processing.
*
* @param value - the object to walk
* @returns the walked object
*/
function walkObj(value) {
const visited = mapVisited.get(value);
// Note: it is possible that the value is in mapVisited but maps to undefined.
// It is not worth the cost of checking for that case since the result will be the same.
if (visited !== undefined) {
return visited;
}
if (value === null) {
return null;
}
if ('toJSON' in value && typeof value.toJSON === 'function') {
// We trust the toJSON implementation to handle circular references itself.
return rememberObj(value, value.toJSON());
}
if (value instanceof RegExp) {
return rememberObj(value, value.toString());
}
if (value instanceof Map) {
return rememberArrayAndMapValuesInPlace(value, [...value.entries()], (entry) => {
entry[0] = walk(entry[0]);
entry[1] = walk(entry[1]);
return entry;
});
}
if (value instanceof Set) {
return rememberArrayAndMapValuesInPlace(value, [...value.values()]);
}
if (value instanceof String) {
return rememberObj(value, value.toString());
}
if (Array.isArray(value)) {
return rememberArrayAndMapValuesInPlace(value, [...value]);
}
// Plain object - process its properties
const obj = rememberObj(value, {});
return Object.assign(obj, Object.fromEntries(Object.entries(value).map(([key, val]) => [key, walk(val)])));
}
return walk(value);
}
//# sourceMappingURL=settingsToJson.js.map