flatten-to-key-value
Version:
Flatten JSON-like values into dot-path key/value pairs or unFlatten them back into nested objects, with support for arrays, dates, and customizable options.
124 lines (123 loc) • 3.86 kB
JavaScript
// src/index.ts
function flattenToKeyValue(input, opts = {}) {
const delimiter = opts.delimiter ?? ".";
const joiner = opts.joiner ?? ",";
const includeNullUndefined = opts.includeNullUndefined ?? false;
const dedupe = opts.dedupeArrayValues ?? false;
const acc = /* @__PURE__ */ new Map();
const add = (key, val) => {
if (!key) return;
const list = acc.get(key);
if (list) {
if (!dedupe || !list.includes(val)) list.push(val);
} else {
acc.set(key, [val]);
}
};
const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
const isPrimitive = (v) => v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean" || typeof v === "bigint";
const toStr = (v) => {
if (v === null || v === void 0) {
return includeNullUndefined ? String(v) : void 0;
}
if (v instanceof Date) return v.toISOString();
if (isPrimitive(v)) return String(v);
try {
return JSON.stringify(v);
} catch {
return String(v);
}
};
const walk = (val, path) => {
if (isPrimitive(val) || val instanceof Date) {
const s2 = toStr(val);
if (s2 !== void 0) add(path, s2);
return;
}
if (Array.isArray(val)) {
if (val.every(isPrimitive)) {
for (const item of val) {
const s2 = toStr(item);
if (s2 !== void 0) add(path, s2);
}
return;
}
for (const item of val) {
if (isPrimitive(item) || item instanceof Date) {
const s2 = toStr(item);
if (s2 !== void 0) add(path, s2);
} else if (Array.isArray(item)) {
walk(item, path);
} else if (isPlainObject(item)) {
for (const [k, v] of Object.entries(item)) {
const next = path ? `${path}${delimiter}${k}` : k;
walk(v, next);
}
}
}
return;
}
if (isPlainObject(val)) {
for (const [k, v] of Object.entries(val)) {
const next = path ? `${path}${delimiter}${k}` : k;
walk(v, next);
}
return;
}
const s = toStr(val);
if (s !== void 0) add(path, s);
};
walk(input, "");
const entries = Array.from(acc.entries()).map(([k, arr]) => [
k,
arr.join(joiner)
]);
if (opts.sortKeys) entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
return Object.fromEntries(entries);
}
function unFlattenKeyValue(input, opts = {}) {
const delimiter = opts.delimiter ?? ".";
const joiner = opts.joiner ?? ",";
const result = {};
const setDeep = (obj, path, value) => {
let curr = obj;
for (let i = 0; i < path.length; i++) {
const key = path[i];
if (i === path.length - 1) {
curr[key] = value;
} else {
if (!(key in curr) || typeof curr[key] !== "object") {
curr[key] = {};
}
curr = curr[key];
}
}
};
for (const [flatKey, strVal] of Object.entries(input)) {
const path = flatKey.split(delimiter);
const parts = strVal.split(joiner).map((s) => s.trim());
const parsedValues = parts.map((s) => {
if (s === "null") return null;
if (s === "undefined") return void 0;
if (s === "true") return true;
if (s === "false") return false;
if (!isNaN(Number(s)) && s.trim() !== "") return Number(s);
const d = new Date(s);
if (!isNaN(d.getTime()) && /^\d{4}-\d{2}-\d{2}T/.test(s)) {
return d;
}
try {
return JSON.parse(s);
} catch {
return s;
}
});
const value = parsedValues.length === 1 ? parsedValues[0] : parsedValues;
setDeep(result, path, value);
}
return result;
}
var index_default = flattenToKeyValue;
export { index_default as default, flattenToKeyValue, unFlattenKeyValue };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map