Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handled unhandled exceptions when flattening objects #185

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 105 additions & 94 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,157 +1,168 @@
function isBuffer (obj) {
return obj &&
function isBuffer(obj) {
return (
obj &&
obj.constructor &&
(typeof obj.constructor.isBuffer === 'function') &&
typeof obj.constructor.isBuffer === "function" &&
obj.constructor.isBuffer(obj)
);
}

function keyIdentity (key) {
return key
function keyIdentity(key) {
return key;
}

export function flatten (target, opts) {
opts = opts || {}
export function flatten(target, opts) {
opts = opts || {};

const delimiter = opts.delimiter || '.'
const maxDepth = opts.maxDepth
const transformKey = opts.transformKey || keyIdentity
const output = {}
if (target === null || target === undefined) {
return target;
}

const delimiter = opts.delimiter || ".";
const maxDepth = opts.maxDepth;
const transformKey = opts.transformKey || keyIdentity;
const output = {};

const seenObjects = new WeakSet();

function step(object, prev, currentDepth) {
currentDepth = currentDepth || 1;

if (seenObjects.has(object)) {
throw new Error("Circular reference detected");
}

seenObjects.add(object);

function step (object, prev, currentDepth) {
currentDepth = currentDepth || 1
Object.keys(object).forEach(function (key) {
const value = object[key]
const isarray = opts.safe && Array.isArray(value)
const type = Object.prototype.toString.call(value)
const isbuffer = isBuffer(value)
const isobject = (
type === '[object Object]' ||
type === '[object Array]'
)
const value = object[key];
const isarray = opts.safe && Array.isArray(value);
const type = Object.prototype.toString.call(value);
const isbuffer = isBuffer(value);
const isobject = type === "[object Object]" || type === "[object Array]";

const newKey = prev
? prev + delimiter + transformKey(key)
: transformKey(key)

if (!isarray && !isbuffer && isobject && Object.keys(value).length &&
(!opts.maxDepth || currentDepth < maxDepth)) {
return step(value, newKey, currentDepth + 1)
: transformKey(key);

if (
!isarray &&
!isbuffer &&
isobject &&
Object.keys(value).length &&
(!opts.maxDepth || currentDepth < maxDepth)
) {
return step(value, newKey, currentDepth + 1);
}

output[newKey] = value
})
output[newKey] = value;
});

const symbols = Object.getOwnPropertySymbols(object);
symbols.forEach((symbol) => {
const value = object[symbol];
const newKey = prev ? prev + delimiter + String(symbol) : String(symbol);
output[newKey] = value;
});
}

step(target)
step(target);

return output
return output;
}

export function unflatten (target, opts) {
opts = opts || {}
export function unflatten(target, opts) {
opts = opts || {};

const delimiter = opts.delimiter || '.'
const overwrite = opts.overwrite || false
const transformKey = opts.transformKey || keyIdentity
const result = {}
const delimiter = opts.delimiter || ".";
const overwrite = opts.overwrite || false;
const transformKey = opts.transformKey || keyIdentity;
const result = {};

const isbuffer = isBuffer(target)
if (isbuffer || Object.prototype.toString.call(target) !== '[object Object]') {
return target
const isbuffer = isBuffer(target);
if (
isbuffer ||
Object.prototype.toString.call(target) !== "[object Object]"
) {
return target;
}

// safely ensure that the key is
// an integer.
function getkey (key) {
const parsedKey = Number(key)

return (
isNaN(parsedKey) ||
key.indexOf('.') !== -1 ||
opts.object
)
function getkey(key) {
const parsedKey = Number(key);

return isNaN(parsedKey) || key.indexOf(".") !== -1 || opts.object
? key
: parsedKey
: parsedKey;
}

function addKeys (keyPrefix, recipient, target) {
function addKeys(keyPrefix, recipient, target) {
return Object.keys(target).reduce(function (result, key) {
result[keyPrefix + delimiter + key] = target[key]
result[keyPrefix + delimiter + key] = target[key];

return result
}, recipient)
return result;
}, recipient);
}

function isEmpty (val) {
const type = Object.prototype.toString.call(val)
const isArray = type === '[object Array]'
const isObject = type === '[object Object]'
function isEmpty(val) {
const type = Object.prototype.toString.call(val);
const isArray = type === "[object Array]";
const isObject = type === "[object Object]";

if (!val) {
return true
return true;
} else if (isArray) {
return !val.length
return !val.length;
} else if (isObject) {
return !Object.keys(val).length
return !Object.keys(val).length;
}
}

target = Object.keys(target).reduce(function (result, key) {
const type = Object.prototype.toString.call(target[key])
const isObject = (type === '[object Object]' || type === '[object Array]')
const type = Object.prototype.toString.call(target[key]);
const isObject = type === "[object Object]" || type === "[object Array]";
if (!isObject || isEmpty(target[key])) {
result[key] = target[key]
return result
result[key] = target[key];
return result;
} else {
return addKeys(
key,
result,
flatten(target[key], opts)
)
return addKeys(key, result, flatten(target[key], opts));
}
}, {})
}, {});

Object.keys(target).forEach(function (key) {
const split = key.split(delimiter).map(transformKey)
let key1 = getkey(split.shift())
let key2 = getkey(split[0])
let recipient = result
const split = key.split(delimiter).map(transformKey);
let key1 = getkey(split.shift());
let key2 = getkey(split[0]);
let recipient = result;

while (key2 !== undefined) {
if (key1 === '__proto__') {
return
if (key1 === "__proto__") {
return;
}

const type = Object.prototype.toString.call(recipient[key1])
const isobject = (
type === '[object Object]' ||
type === '[object Array]'
)
const type = Object.prototype.toString.call(recipient[key1]);
const isobject = type === "[object Object]" || type === "[object Array]";

// do not write over falsey, non-undefined values if overwrite is false
if (!overwrite && !isobject && typeof recipient[key1] !== 'undefined') {
return
if (!overwrite && !isobject && typeof recipient[key1] !== "undefined") {
return;
}

if ((overwrite && !isobject) || (!overwrite && recipient[key1] == null)) {
recipient[key1] = (
typeof key2 === 'number' &&
!opts.object
? []
: {}
)
recipient[key1] = typeof key2 === "number" && !opts.object ? [] : {};
}

recipient = recipient[key1]
recipient = recipient[key1];
if (split.length > 0) {
key1 = getkey(split.shift())
key2 = getkey(split[0])
key1 = getkey(split.shift());
key2 = getkey(split[0]);
}
}

// unflatten again for 'messy objects'
recipient[key1] = unflatten(target[key], opts)
})
recipient[key1] = unflatten(target[key], opts);
});

return result
return result;
}
28 changes: 28 additions & 0 deletions test/unhandledExceptionsTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import assert from "node:assert";
import { describe, test } from "node:test";
import { flatten, unflatten } from "../index.js";

describe("flatten obj tests", function () {
test("Flatten null", function () {
assert.deepStrictEqual(flatten(null), null);
});

test("Flatten undefined", function () {
assert.deepStrictEqual(flatten(undefined), undefined);
});

test("Flatten object with Symbol keys", function () {
const sym = Symbol("test");
const obj = { [sym]: "value", regularKey: "regularValue" };
const flatObj = flatten(obj);

assert.strictEqual(flatObj["Symbol(test)"], "value"); // Assuming stringification of symbol keys
});

test("Flatten circular reference", function () {
const obj = { name: "John" };
obj.self = obj; // Circular reference

assert.throws(() => flatten(obj), /Circular reference detected/);
});
});