Given I have a circular reference in a large JavaScript object
And I try JSON.stringify(problematicObject)
And the browser throws
"TypeError: Converting circular structure to JSON"
(which is expected)
Then I want to find the cause of this circular reference, preferably using Chrome developer tools? Is this possible? How do you find and fix circular references in a large object?
Pulled from http://blog.vjeux.com/2011/javascript/cyclic-object-detection.html. One line added to detect where the cycle is. Paste this into the Chrome dev tools:
function isCyclic (obj) {
var seenObjects = [];
function detect (obj) {
if (obj && typeof obj === 'object') {
if (seenObjects.indexOf(obj) !== -1) {
return true;
}
seenObjects.push(obj);
for (var key in obj) {
if (obj.hasOwnProperty(key) && detect(obj[key])) {
console.log(obj, 'cycle at ' + key);
return true;
}
}
}
return false;
}
return detect(obj);
}
Here's the test:
> a = {}
> b = {}
> a.b = b; b.a = a;
> isCyclic(a)
Object {a: Object}
"cycle at a"
Object {b: Object}
"cycle at b"
true
#tmack's answer is definitely what I was looking for when I found this question!
Unfortunately it returns many false positives - it returns true if an object is replicated in the JSON, which isn't the same as circularity. Circularity means that an object is its own child, e.g.
obj.key1.key2.[...].keyX === obj
I modified the original answer, and this is working for me:
function isCyclic(obj) {
var keys = [];
var stack = [];
var stackSet = new Set();
var detected = false;
function detect(obj, key) {
if (obj && typeof obj != 'object') { return; }
if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
var oldindex = stack.indexOf(obj);
var l1 = keys.join('.') + '.' + key;
var l2 = keys.slice(0, oldindex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (var k in obj) { //dive on the object's children
if (Object.prototype.hasOwnProperty.call(obj, k)) { detect(obj[k], k); }
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(obj, 'obj');
return detected;
}
Here are a few very simple tests:
var root = {}
var leaf = {'isleaf':true};
var cycle2 = {l:leaf};
var cycle1 = {c2: cycle2, l:leaf};
cycle2.c1 = cycle1
root.leaf = leaf
isCyclic(cycle1); // returns true, logs "CIRCULAR: obj.c2.c1 = obj"
isCyclic(cycle2); // returns true, logs "CIRCULAR: obj.c1.c2 = obj"
isCyclic(leaf); // returns false
isCyclic(root); // returns false
Here is MDN's approach to detecting and fixing circular references when using JSON.stringify() on circular objects: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value :
In a circular structure like the following
var circularReference = {otherData: 123};
circularReference.myself = circularReference;
JSON.stringify() will fail:
JSON.stringify(circularReference);
// TypeError: cyclic object value
To serialize circular references you can use a library that supports them (e.g. cycle.js) or implement a solution by yourself, which will require finding and replacing (or removing) the cyclic references by serializable values.
The snippet below illustrates how to find and filter (thus causing data loss) a cyclic reference by using the replacer parameter of JSON.stringify():
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
JSON.stringify(circularReference, getCircularReplacer());
// {"otherData":123}
You can also use JSON.stringify with try/catch
function hasCircularDependency(obj)
{
try
{
JSON.stringify(obj);
}
catch(e)
{
return e.includes("Converting circular structure to JSON");
}
return false;
}
Demo
function hasCircularDependency(obj) {
try {
JSON.stringify(obj);
} catch (e) {
return String(e).includes("Converting circular structure to JSON");
}
return false;
}
var a = {b:{c:{d:""}}};
console.log(hasCircularDependency(a));
a.b.c.d = a;
console.log(hasCircularDependency(a));
This is a fix for both #Trey Mack and #Freddie Nfbnm answers on the typeof obj != 'object' condition. Instead it should test if the obj value is not instance of object, so that it can also work when checking values with object familiarity (for example, functions and symbols (symbols aren't instance of object, but still addressed, btw.)).
I'm posting this as an answer since I can't comment in this StackExchange account yet.
PS.: feel free to request me to delete this answer.
function isCyclic(obj) {
var keys = [];
var stack = [];
var stackSet = new Set();
var detected = false;
function detect(obj, key) {
if (!(obj instanceof Object)) { return; } // Now works with other
// kinds of object.
if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
var oldindex = stack.indexOf(obj);
var l1 = keys.join('.') + '.' + key;
var l2 = keys.slice(0, oldindex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (var k in obj) { //dive on the object's children
if (obj.hasOwnProperty(k)) { detect(obj[k], k); }
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(obj, 'obj');
return detected;
}
CircularReferenceDetector
Here is my CircularReferenceDetector class which outputs all the property stack information where the circularly referenced value is actually located at and also shows where the culprit references are.
This is especially useful for huge structures where it is not obvious by the key which value is the source of the harm.
It outputs the circularly referenced value stringified but all references to itself replaced by "[Circular object --- fix me]".
Usage:
CircularReferenceDetector.detectCircularReferences(value);
Note:
Remove the Logger.* statements if you do not want to use any logging or do not have a logger available.
Technical Explanation:
The recursive function goes through all properties of the object and tests if JSON.stringify succeeds on them or not.
If it does not succeed (circular reference), then it tests if it succeeds by replacing value itself with some constant string. This would mean that if it succeeds using this replacer, this value is the being circularly referenced value. If it is not, it recursively goes through all properties of that object.
Meanwhile it also tracks the property stack to give you information where the culprit value is located at.
Typescript
import {Logger} from "../Logger";
export class CircularReferenceDetector {
static detectCircularReferences(toBeStringifiedValue: any, serializationKeyStack: string[] = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
var value = toBeStringifiedValue[key];
var serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is ok`);
} catch (error) {
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
var isCircularValue:boolean;
var circularExcludingStringifyResult:string = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
} catch (error) {
Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is not the circular source`);
CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${Util.joinStrings(serializationKeyStackWithNewKey)}" of the given root object\n`+
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n${circularExcludingStringifyResult}\n`);
}
}
});
}
private static replaceRootStringifyReplacer(toBeStringifiedValue: any): any {
var serializedObjectCounter = 0;
return function (key: any, value: any) {
if (serializedObjectCounter !== 0 && typeof(toBeStringifiedValue) === 'object' && toBeStringifiedValue === value) {
Logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return '[Circular object --- fix me]';
}
serializedObjectCounter++;
return value;
}
}
}
export class Util {
static joinStrings(arr: string[], separator: string = ":") {
if (arr.length === 0) return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
}
Compiled JavaScript from TypeScript
"use strict";
const Logger_1 = require("../Logger");
class CircularReferenceDetector {
static detectCircularReferences(toBeStringifiedValue, serializationKeyStack = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
var value = toBeStringifiedValue[key];
var serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is ok`);
}
catch (error) {
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
var isCircularValue;
var circularExcludingStringifyResult = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, CircularReferenceDetector.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
}
catch (error) {
Logger_1.Logger.debug(`path "${Util.joinStrings(serializationKeyStack)}" is not the circular source`);
CircularReferenceDetector.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${Util.joinStrings(serializationKeyStackWithNewKey)}" of the given root object\n` +
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n${circularExcludingStringifyResult}\n`);
}
}
});
}
static replaceRootStringifyReplacer(toBeStringifiedValue) {
var serializedObjectCounter = 0;
return function (key, value) {
if (serializedObjectCounter !== 0 && typeof (toBeStringifiedValue) === 'object' && toBeStringifiedValue === value) {
Logger_1.Logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return '[Circular object --- fix me]';
}
serializedObjectCounter++;
return value;
};
}
}
exports.CircularReferenceDetector = CircularReferenceDetector;
class Util {
static joinStrings(arr, separator = ":") {
if (arr.length === 0)
return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
}
exports.Util = Util;
Here is a Node ES6 version mixed from the answers from #Aaron V and #user4976005, it fixes the problem with the call to hasOwnProperty:
const isCyclic = (obj => {
const keys = []
const stack = []
const stackSet = new Set()
let detected = false
const detect = ((object, key) => {
if (!(object instanceof Object))
return
if (stackSet.has(object)) { // it's cyclic! Print the object and its locations.
const oldindex = stack.indexOf(object)
const l1 = `${keys.join('.')}.${key}`
const l2 = keys.slice(0, oldindex + 1).join('.')
console.log(`CIRCULAR: ${l1} = ${l2} = ${object}`)
console.log(object)
detected = true
return
}
keys.push(key)
stack.push(object)
stackSet.add(object)
Object.keys(object).forEach(k => { // dive on the object's children
if (k && Object.prototype.hasOwnProperty.call(object, k))
detect(object[k], k)
})
keys.pop()
stack.pop()
stackSet.delete(object)
})
detect(obj, 'obj')
return detected
})
There's a lot of answers here, but I thought I'd add my solution to the mix. It's similar to #Trey Mack's answer, but that solution takes O(n^2). This version uses WeakMap instead of an array, improving the time to O(n).
function isCyclic(object) {
const seenObjects = new WeakMap(); // use to keep track of which objects have been seen.
function detectCycle(obj) {
// If 'obj' is an actual object (i.e., has the form of '{}'), check
// if it's been seen already.
if (Object.prototype.toString.call(obj) == '[object Object]') {
if (seenObjects.has(obj)) {
return true;
}
// If 'obj' hasn't been seen, add it to 'seenObjects'.
// Since 'obj' is used as a key, the value of 'seenObjects[obj]'
// is irrelevent and can be set as literally anything you want. I
// just went with 'undefined'.
seenObjects.set(obj, undefined);
// Recurse through the object, looking for more circular references.
for (var key in obj) {
if (detectCycle(obj[key])) {
return true;
}
}
// If 'obj' is an array, check if any of it's elements are
// an object that has been seen already.
} else if (Array.isArray(obj)) {
for (var i in obj) {
if (detectCycle(obj[i])) {
return true;
}
}
}
return false;
}
return detectCycle(object);
}
And this is what it looks like in action.
> var foo = {grault: {}};
> detectCycle(foo);
false
> foo.grault = foo;
> detectCycle(foo);
true
> var bar = {};
> detectCycle(bar);
false
> bar.plugh = [];
> bar.plugh.push(bar);
> detectCycle(bar);
true
You can also use Symbols - thanks to that approach you won't have to mutate properties of the original object, apart from adding symbol for marking visited node.
It's cleaner and should be faster than gathering node properties and comparing with the object. It also has optional depth limitation if you don't want to serialize big nested values:
// Symbol used to mark already visited nodes - helps with circular dependencies
const visitedMark = Symbol('VISITED_MARK');
const MAX_CLEANUP_DEPTH = 10;
function removeCirculars(obj, depth = 0) {
if (!obj) {
return obj;
}
// Skip condition - either object is falsy, was visited or we go too deep
const shouldSkip = !obj || obj[visitedMark] || depth > MAX_CLEANUP_DEPTH;
// Copy object (we copy properties from it and mark visited nodes)
const originalObj = obj;
let result = {};
Object.keys(originalObj).forEach((entry) => {
const val = originalObj[entry];
if (!shouldSkip) {
if (typeof val === 'object') { // Value is an object - run object sanitizer
originalObj[visitedMark] = true; // Mark current node as "seen" - will stop from going deeper into circulars
const nextDepth = depth + 1;
result[entry] = removeCirculars(val, nextDepth);
} else {
result[entry] = val;
}
} else {
result = 'CIRCULAR';
}
});
return result;
}
This will result in an object that has all the circular dependencies stripped and also does not go deeper than given MAX_CLEANUP_DEPTH.
Using symbols is safe as long as you don't do any meta-programming stuff on the object - they are transparent and they are not enumerable, hence - they will not show in any standard operations on the object.
Also, returning a new, cleaned up object has an advantage of not mutating the original one if you need to perform any additional operations on it.
If you don't want CIRCULAR marking, you can just modify the code a bit, hence skipping object before actually performing operations on it (inside the loop):
originalObj[visitedMark] = true; // Mark current node as "seen" - will stop from going deeper into circulars
const val = originalObj[entry];
// Skip condition - either object is falsy, was visited or we go too deep
const shouldSkip = val[visitedMark] || depth > MAX_SANITIZATION_DEPTH;
if (!shouldSkip) {
if (typeof val === 'object') { // Value is an object - run object sanitizer
const nextDepth = depth + 1;
result[entry] = removeCirculars(val, nextDepth);
} else {
result[entry] = val;
}
}
I just made this. It may be dirty, but works anyway... :P
function dump(orig){
var inspectedObjects = [];
console.log('== DUMP ==');
(function _dump(o,t){
console.log(t+' Type '+(typeof o));
for(var i in o){
if(o[i] === orig){
console.log(t+' '+i+': [recursive]');
continue;
}
var ind = 1+inspectedObjects.indexOf(o[i]);
if(ind>0) console.log(t+' '+i+': [already inspected ('+ind+')]');
else{
console.log(t+' '+i+': ('+inspectedObjects.push(o[i])+')');
_dump(o[i],t+'>>');
}
}
}(orig,'>'));
}
Then
var a = [1,2,3], b = [a,4,5,6], c = {'x':a,'y':b};
a.push(c); dump(c);
Says
== DUMP ==
> Type object
> x: (1)
>>> Type object
>>> 0: (2)
>>>>> Type number
>>> 1: (3)
>>>>> Type number
>>> 2: (4)
>>>>> Type number
>>> 3: [recursive]
> y: (5)
>>> Type object
>>> 0: [already inspected (1)]
>>> 1: (6)
>>>>> Type number
>>> 2: (7)
>>>>> Type number
>>> 3: (8)
>>>>> Type number
This tells that c.x[3] is equal to c, and c.x = c.y[0].
Or, a little edit to this function can tell you what you need...
function findRecursive(orig){
var inspectedObjects = [];
(function _find(o,s){
for(var i in o){
if(o[i] === orig){
console.log('Found: obj.'+s.join('.')+'.'+i);
return;
}
if(inspectedObjects.indexOf(o[i])>=0) continue;
else{
inspectedObjects.push(o[i]);
s.push(i); _find(o[i],s); s.pop(i);
}
}
}(orig,[]));
}
Here is #Thomas's answer adapted for node:
const {logger} = require("../logger")
// Or: const logger = {debug: (...args) => console.log.call(console.log, args) }
const joinStrings = (arr, separator) => {
if (arr.length === 0) return "";
return arr.reduce((v1, v2) => `${v1}${separator}${v2}`);
}
exports.CircularReferenceDetector = class CircularReferenceDetector {
detectCircularReferences(toBeStringifiedValue, serializationKeyStack = []) {
Object.keys(toBeStringifiedValue).forEach(key => {
let value = toBeStringifiedValue[key];
let serializationKeyStackWithNewKey = serializationKeyStack.slice();
serializationKeyStackWithNewKey.push(key);
try {
JSON.stringify(value);
logger.debug(`path "${joinStrings(serializationKeyStack)}" is ok`);
} catch (error) {
logger.debug(`path "${joinStrings(serializationKeyStack)}" JSON.stringify results in error: ${error}`);
let isCircularValue;
let circularExcludingStringifyResult = "";
try {
circularExcludingStringifyResult = JSON.stringify(value, this.replaceRootStringifyReplacer(value), 2);
isCircularValue = true;
} catch (error) {
logger.debug(`path "${joinStrings(serializationKeyStack)}" is not the circular source`);
this.detectCircularReferences(value, serializationKeyStackWithNewKey);
isCircularValue = false;
}
if (isCircularValue) {
throw new Error(`Circular reference detected:\nCircularly referenced value is value under path "${joinStrings(serializationKeyStackWithNewKey)}" of the given root object\n`+
`Calling stringify on this value but replacing itself with [Circular object --- fix me] ( <-- search for this string) results in:\n${circularExcludingStringifyResult}\n`);
}
}
});
}
replaceRootStringifyReplacer(toBeStringifiedValue) {
let serializedObjectCounter = 0;
return function (key, value) {
if (serializedObjectCounter !== 0 && typeof(toBeStringifiedValue) === 'object' && toBeStringifiedValue === value) {
logger.error(`object serialization with key ${key} has circular reference to being stringified object`);
return '[Circular object --- fix me]';
}
serializedObjectCounter++;
return value;
}
}
}
I converted the answer of Freddie Nfbnm to TypeScript:
export class JsonUtil {
static isCyclic(json) {
const keys = [];
const stack = [];
const stackSet = new Set();
let detected = false;
function detect(obj, key) {
if (typeof obj !== 'object') {
return;
}
if (stackSet.has(obj)) { // it's cyclic! Print the object and its locations.
const oldIndex = stack.indexOf(obj);
const l1 = keys.join('.') + '.' + key;
const l2 = keys.slice(0, oldIndex + 1).join('.');
console.log('CIRCULAR: ' + l1 + ' = ' + l2 + ' = ' + obj);
console.log(obj);
detected = true;
return;
}
keys.push(key);
stack.push(obj);
stackSet.add(obj);
for (const k in obj) { // dive on the object's children
if (obj.hasOwnProperty(k)) {
detect(obj[k], k);
}
}
keys.pop();
stack.pop();
stackSet.delete(obj);
return;
}
detect(json, 'obj');
return detected;
}
}
Just to throw my version into the mix... below is a remix of #dkurzaj 's code (which is itself a remix of #Aaron V 's, #user4976005 's, #Trey Mack 's and finally #Freddie Nfbnm 's [removed?] code) plus #darksinge 's WeakMap idea. So... this thread's Megamix, I guess :)
In my version, a report (rather than console.log'ed entries) is optionally returned as an array of objects. If a report is not required, testing stops on the first sighting of a circular reference (a'la #darksinge 's code).
Further, hasOwnProperty has been removed as Object.keys returns only hasOwnProperty properties (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys ).
function isCyclic(x, bReturnReport) {
var a_sKeys = [],
a_oStack = [],
wm_oSeenObjects = new WeakMap(), //# see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
oReturnVal = {
found: false,
report: []
}
;
//# Setup the recursive logic to locate any circular references while kicking off the initial call
(function doIsCyclic(oTarget, sKey) {
var a_sTargetKeys, sCurrentKey, i;
//# If we've seen this oTarget before, flip our .found to true
if (wm_oSeenObjects.has(oTarget)) {
oReturnVal.found = true;
//# If we are to bReturnReport, add the entries into our .report
if (bReturnReport) {
oReturnVal.report.push({
instance: oTarget,
source: a_sKeys.slice(0, a_oStack.indexOf(oTarget) + 1).join('.'),
duplicate: a_sKeys.join('.') + "." + sKey
});
}
}
//# Else if oTarget is an instanceof Object, determine the a_sTargetKeys and .set our oTarget into the wm_oSeenObjects
else if (oTarget instanceof Object) {
a_sTargetKeys = Object.keys(oTarget);
wm_oSeenObjects.set(oTarget /*, undefined*/);
//# If we are to bReturnReport, .push the current level's/call's items onto our stacks
if (bReturnReport) {
if (sKey) { a_sKeys.push(sKey) };
a_oStack.push(oTarget);
}
//# Traverse the a_sTargetKeys, pulling each into sCurrentKey as we go
//# NOTE: If you want all properties, even non-enumerables, see Object.getOwnPropertyNames() so there is no need to call .hasOwnProperty (per: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys)
for (i = 0; i < a_sTargetKeys.length; i++) {
sCurrentKey = a_sTargetKeys[i];
//# If we've already .found a circular reference and we're not bReturnReport, fall from the loop
if (oReturnVal.found && !bReturnReport) {
break;
}
//# Else if the sCurrentKey is an instanceof Object, recurse to test
else if (oTarget[sCurrentKey] instanceof Object) {
doIsCyclic(oTarget[sCurrentKey], sCurrentKey);
}
}
//# .delete our oTarget into the wm_oSeenObjects
wm_oSeenObjects.delete(oTarget);
//# If we are to bReturnReport, .pop the current level's/call's items off our stacks
if (bReturnReport) {
if (sKey) { a_sKeys.pop() };
a_oStack.pop();
}
}
}(x, '')); //# doIsCyclic
return (bReturnReport ? oReturnVal.report : oReturnVal.found);
}
Most of the other answers only show how to detect that an object-tree has a circular-reference -- they don't tell you how to fix those circular references (ie. replacing the circular-reference values with, eg. undefined).
The below is the function I use to replace all circular-references with undefined:
export const specialTypeHandlers_default = [
// Set and Map are included by default, since JSON.stringify tries (and fails) to serialize them by default
{type: Set, keys: a=>a.keys(), get: (a, key)=>key, delete: (a, key)=>a.delete(key)},
{type: Map, keys: a=>a.keys(), get: (a, key)=>a.get(key), delete: (a, key)=>a.set(key, undefined)},
];
export function RemoveCircularLinks(node, specialTypeHandlers = specialTypeHandlers_default, nodeStack_set = new Set()) {
nodeStack_set.add(node);
const specialHandler = specialTypeHandlers.find(a=>node instanceof a.type);
for (const key of specialHandler ? specialHandler.keys(node) : Object.keys(node)) {
const value = specialHandler ? specialHandler.get(node, key) : node[key];
// if the value is already part of visited-stack, delete the value (and don't tunnel into it)
if (nodeStack_set.has(value)) {
if (specialHandler) specialHandler.delete(node, key);
else node[key] = undefined;
}
// else, tunnel into it, looking for circular-links at deeper levels
else if (typeof value == "object" && value != null) {
RemoveCircularLinks(value, specialTypeHandlers, nodeStack_set);
}
}
nodeStack_set.delete(node);
}
For use with JSON.stringify specifically, simply call the function above prior to the stringification (note that it does mutate the passed-in object):
const objTree = {normalProp: true};
objTree.selfReference = objTree;
RemoveCircularLinks(objTree); // without this line, the JSON.stringify call errors
console.log(JSON.stringify(objTree));
if you just need to see the content of that circular object, just use console.table(circularObj)
Try using console.log() on the chrome/firefox browser to identify where the issue encountered.
On Firefox using Firebug plugin, you can debug your javascript line by line.
Update:
Refer below example of circular reference issue and which has been handled:-
// JSON.stringify, avoid TypeError: Converting circular structure to JSON
// Demo: Circular reference
var o = {};
o.o = o;
var cache = [];
JSON.stringify(o, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Circular reference found, discard key
alert("Circular reference found, discard key");
return;
}
alert("value = '" + value + "'");
// Store value in our collection
cache.push(value);
}
return value;
});
cache = null; // Enable garbage collection
var a = {b:1};
var o = {};
o.one = a;
o.two = a;
// one and two point to the same object, but two is discarded:
JSON.stringify(o);
var obj = {
a: "foo",
b: obj
};
var replacement = {"b":undefined};
alert("Result : " + JSON.stringify(obj,replacement));
Refer example LIVE DEMO
Related
I am trying to create a deep copy map method for my Redux project that will work with objects rather than arrays. I read that in Redux each state should not change anything in the previous states.
export const mapCopy = (object, callback) => {
return Object.keys(object).reduce(function (output, key) {
output[key] = callback.call(this, {...object[key]});
return output;
},
{});
}
It works:
return mapCopy(state, e => {
if (e.id === action.id) {
e.title = 'new item';
}
return e;
})
However it does not deep copy inner items so I need to tweak it to:
export const mapCopy = (object, callback) => {
return Object.keys(object).reduce(function (output, key) {
let newObject = {...object[key]};
newObject.style = {...newObject.style};
newObject.data = {...newObject.data};
output[key] = callback.call(this, newObject);
return output;
}, {});
}
This is less elegant as it requires to know which objects are passed.
Is there a way in ES6 to use the spread syntax to deep copy an object?
Use JSON for deep copy
var newObject = JSON.parse(JSON.stringify(oldObject))
var oldObject = {
name: 'A',
address: {
street: 'Station Road',
city: 'Pune'
}
}
var newObject = JSON.parse(JSON.stringify(oldObject));
newObject.address.city = 'Delhi';
console.log('newObject');
console.log(newObject);
console.log('oldObject');
console.log(oldObject);
No such functionality is built-in to ES6. I think you have a couple of options depending on what you want to do.
If you really want to deep copy:
Use a library. For example, lodash has a cloneDeep method.
Implement your own cloning function.
Alternative Solution To Your Specific Problem (No Deep Copy)
However, I think, if you're willing to change a couple things, you can save yourself some work. I'm assuming you control all call sites to your function.
Specify that all callbacks passed to mapCopy must return new objects instead of mutating the existing object. For example:
mapCopy(state, e => {
if (e.id === action.id) {
return Object.assign({}, e, {
title: 'new item'
});
} else {
return e;
}
});
This makes use of Object.assign to create a new object, sets properties of e on that new object, then sets a new title on that new object. This means you never mutate existing objects and only create new ones when necessary.
mapCopy can be really simple now:
export const mapCopy = (object, callback) => {
return Object.keys(object).reduce(function (output, key) {
output[key] = callback.call(this, object[key]);
return output;
}, {});
}
Essentially, mapCopy is trusting its callers to do the right thing. This is why I said this assumes you control all call sites.
From MDN
Note: Spread syntax effectively goes one level deep while copying an array. Therefore, it may be unsuitable for copying multidimensional arrays as the following example shows (it's the same with Object.assign() and spread syntax).
Personally, I suggest using Lodash's cloneDeep function for multi-level object/array cloning.
Here is a working example:
const arr1 = [{ 'a': 1 }];
const arr2 = [...arr1];
const arr3 = _.clone(arr1);
const arr4 = arr1.slice();
const arr5 = _.cloneDeep(arr1);
const arr6 = [...{...arr1}]; // a bit ugly syntax but it is working!
// first level
console.log(arr1 === arr2); // false
console.log(arr1 === arr3); // false
console.log(arr1 === arr4); // false
console.log(arr1 === arr5); // false
console.log(arr1 === arr6); // false
// second level
console.log(arr1[0] === arr2[0]); // true
console.log(arr1[0] === arr3[0]); // true
console.log(arr1[0] === arr4[0]); // true
console.log(arr1[0] === arr5[0]); // false
console.log(arr1[0] === arr6[0]); // false
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
I often use this:
function deepCopy(obj) {
if(typeof obj !== 'object' || obj === null) {
return obj;
}
if(obj instanceof Date) {
return new Date(obj.getTime());
}
if(obj instanceof Array) {
return obj.reduce((arr, item, i) => {
arr[i] = deepCopy(item);
return arr;
}, []);
}
if(obj instanceof Object) {
return Object.keys(obj).reduce((newObj, key) => {
newObj[key] = deepCopy(obj[key]);
return newObj;
}, {})
}
}
You can use structuredClone() like the following:
const myOriginal = {
title: "Full Stack JavaScript Developer",
info: {
firstname: "Abolfazl",
surname: "Roshanzamir",
age: 34
}
};
const myDeepCopy = structuredClone(myOriginal);
structuredClone()
You can use structuredClone() that is a built-in function for deep-copying.
Structured cloning addresses many (although not all) shortcomings of the JSON.stringify() technique.
Structured cloning can handle cyclical data structures,
support many built-in data types, and is generally more robust and often faster.
However, it still has some limitations that may catch you off-guard:
1-Prototypes : If you use structuredClone() with a class instance,
you’ll get a plain object as the return value, as structured cloning discards the object’s prototype chain.
2-Functions: If your object contains functions, they will be quietly discarded.
3- Non-cloneables: Some values are not structured cloneable, most notably Error and DOM nodes. It will cause structuredClone() to throw.
const myDeepCopy = structuredClone(myOriginal);
JSON.stringify
If you simply want to deep copy the object to another object,
all you will need to do is JSON.stringify the object and parse it using JSON.parse afterward.
This will essentially perform deep copying of the object.
let user1 = {
name: 'Abolfazl Roshanzamir',
age: 34,
university: {
name: 'Shiraz Bahonar University'
}
};
let user2 = JSON.parse(JSON.stringify(user1));
user2.name = 'Andy Madadian';
user2.university.name = 'Kerman Bahonar University'
console.log(user2);
// { name: 'Andy Madadian', age: 33, university: { name: 'Kerman Bahonar University' } }
console.log(user1);
// { name: 'Abolfazl Roshanzamir', age: 33, university: { name: 'Shiraz Bahonar University' } }
Spread operator / Object.assign()
One way to create a shallow copy in JavaScript using the object spread operator ... or Object.assign() like the following:
const myShallowCopySpread = {...myOriginal};
const myShallowCopyObjectAssign=Object.assign({},obj)
Performance
When it comes to performance the creator Surma has pointed out that JSON.Parse() can be a bit faster for small objects. But when you have a large object, complex object
structuredClone() starts to get significantly faster.
Browser support is pretty fantastic And even is supported by Node.js.
const a = {
foods: {
dinner: 'Pasta'
}
}
let b = JSON.parse(JSON.stringify(a))
b.foods.dinner = 'Soup'
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta
Using JSON.stringify and JSON.parse is the best way. Because by using the spread operator we will not get the efficient answer when the json object contains another object inside it. we need to manually specify that.
Here's my deep copy algorithm.
const DeepClone = (obj) => {
if(obj===null||typeof(obj)!=='object')return null;
let newObj = { ...obj };
for (let prop in obj) {
if (
typeof obj[prop] === "object" ||
typeof obj[prop] === "function"
) {
newObj[prop] = DeepClone(obj[prop]);
}
}
return newObj;
};
// use: clone( <thing to copy> ) returns <new copy>
// untested use at own risk
function clone(o, m){
// return non object values
if('object' !==typeof o) return o
// m: a map of old refs to new object refs to stop recursion
if('object' !==typeof m || null ===m) m =new WeakMap()
var n =m.get(o)
if('undefined' !==typeof n) return n
// shallow/leaf clone object
var c =Object.getPrototypeOf(o).constructor
// TODO: specialize copies for expected built in types i.e. Date etc
switch(c) {
// shouldn't be copied, keep reference
case Boolean:
case Error:
case Function:
case Number:
case Promise:
case String:
case Symbol:
case WeakMap:
case WeakSet:
n =o
break;
// array like/collection objects
case Array:
m.set(o, n =o.slice(0))
// recursive copy for child objects
n.forEach(function(v,i){
if('object' ===typeof v) n[i] =clone(v, m)
});
break;
case ArrayBuffer:
m.set(o, n =o.slice(0))
break;
case DataView:
m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.byteLength))
break;
case Map:
case Set:
m.set(o, n =new (c)(clone(Array.from(o.entries()), m)))
break;
case Int8Array:
case Uint8Array:
case Uint8ClampedArray:
case Int16Array:
case Uint16Array:
case Int32Array:
case Uint32Array:
case Float32Array:
case Float64Array:
m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.length))
break;
// use built in copy constructor
case Date:
case RegExp:
m.set(o, n =new (c)(o))
break;
// fallback generic object copy
default:
m.set(o, n =Object.assign(new (c)(), o))
// recursive copy for child objects
for(c in n) if('object' ===typeof n[c]) n[c] =clone(n[c], m)
}
return n
}
Here is the deepClone function which handles all primitive, array, object, function data types
function deepClone(obj){
if(Array.isArray(obj)){
var arr = [];
for (var i = 0; i < obj.length; i++) {
arr[i] = deepClone(obj[i]);
}
return arr;
}
if(typeof(obj) == "object"){
var cloned = {};
for(let key in obj){
cloned[key] = deepClone(obj[key])
}
return cloned;
}
return obj;
}
console.log( deepClone(1) )
console.log( deepClone('abc') )
console.log( deepClone([1,2]) )
console.log( deepClone({a: 'abc', b: 'def'}) )
console.log( deepClone({
a: 'a',
num: 123,
func: function(){'hello'},
arr: [[1,2,3,[4,5]], 'def'],
obj: {
one: {
two: {
three: 3
}
}
}
}) )
function deepclone(obj) {
let newObj = {};
if (typeof obj === 'object') {
for (let key in obj) {
let property = obj[key],
type = typeof property;
switch (type) {
case 'object':
if( Object.prototype.toString.call( property ) === '[object Array]' ) {
newObj[key] = [];
for (let item of property) {
newObj[key].push(this.deepclone(item))
}
} else {
newObj[key] = deepclone(property);
}
break;
default:
newObj[key] = property;
break;
}
}
return newObj
} else {
return obj;
}
}
const cloneData = (dataArray) => {
newData= []
dataArray.forEach((value) => {
newData.push({...value})
})
return newData
}
a = [{name:"siva"}, {name:"siva1"}] ;
b = myCopy(a)
b === a // false`
I myself landed on these answers last day, trying to find a way to deep copy complex structures, which may include recursive links. As I wasn't satisfied with anything being suggested before, I implemented this wheel myself. And it works quite well. Hope it helps someone.
Example usage:
OriginalStruct.deep_copy = deep_copy; // attach the function as a method
TheClone = OriginalStruct.deep_copy();
Please look at https://github.com/latitov/JS_DeepCopy for live examples how to use it, and also deep_print() is there.
If you need it quick, right here's the source of deep_copy() function:
function deep_copy() {
'use strict'; // required for undef test of 'this' below
// Copyright (c) 2019, Leonid Titov, Mentions Highly Appreciated.
var id_cnt = 1;
var all_old_objects = {};
var all_new_objects = {};
var root_obj = this;
if (root_obj === undefined) {
console.log(`deep_copy() error: wrong call context`);
return;
}
var new_obj = copy_obj(root_obj);
for (var id in all_old_objects) {
delete all_old_objects[id].__temp_id;
}
return new_obj;
//
function copy_obj(o) {
var new_obj = {};
if (o.__temp_id === undefined) {
o.__temp_id = id_cnt;
all_old_objects[id_cnt] = o;
all_new_objects[id_cnt] = new_obj;
id_cnt ++;
for (var prop in o) {
if (o[prop] instanceof Array) {
new_obj[prop] = copy_array(o[prop]);
}
else if (o[prop] instanceof Object) {
new_obj[prop] = copy_obj(o[prop]);
}
else if (prop === '__temp_id') {
continue;
}
else {
new_obj[prop] = o[prop];
}
}
}
else {
new_obj = all_new_objects[o.__temp_id];
}
return new_obj;
}
function copy_array(a) {
var new_array = [];
if (a.__temp_id === undefined) {
a.__temp_id = id_cnt;
all_old_objects[id_cnt] = a;
all_new_objects[id_cnt] = new_array;
id_cnt ++;
a.forEach((v,i) => {
if (v instanceof Array) {
new_array[i] = copy_array(v);
}
else if (v instanceof Object) {
new_array[i] = copy_object(v);
}
else {
new_array[i] = v;
}
});
}
else {
new_array = all_new_objects[a.__temp_id];
}
return new_array;
}
}
Cheers#!
I would suggest using the spread operator. You'll need to spread a second time if you need to update the second level. Attempting to update the newObject using something like newObject.address.city will throw an error if address did not already exist in oldObject.
const oldObject = {
name: 'A',
address: {
street: 'Station Road',
city: 'Pune'
}
}
const newObject = {
...oldObject,
address: {
...oldObject.address,
city: 'Delhi'
}
}
console.log(newObject)
This is a very old question but I think in 2022 there are many ways to solve this. However, if you want a simple, fast and vanilla JS solution check this out:
const cloner = (o) => {
let idx = 1
const isArray = (a) => a instanceof Array
const isObject = (o) => o instanceof Object
const isUndefined = (a) => a === undefined
const process = v => {
if (isArray(v)) return cloneArray(v)
else if (isObject(v)) return cloneObject(v)
else return v
}
const register = (old, o) => {
old.__idx = idx
oldObjects[idx] = old
newObjects[idx] = o
idx++
}
const cloneObject = o => {
if (!isUndefined(o.__idx)) return newObjects[o.__idx]
const obj = {}
for (const prop in o) {
if (prop === '__idx') continue
obj[prop] = process(o[prop])
}
register(o, obj)
return obj
}
const cloneArray = a => {
if (!isUndefined(a.__idx)) return newObjects[a.__idx]
const arr = a.map((v) => process(v))
register(a, arr)
return arr
}
const oldObjects = {}
const newObjects = {}
let tmp
if (isArray(o)) tmp = cloneArray(o)
else if (isObject(o)) tmp = cloneObject(o)
else return o
for (const id in oldObjects) delete oldObjects[id].__idx
return tmp
}
const c = {
id: 123,
label: "Lala",
values: ['char', 1, {flag: true}, [1,2,3,4,5], ['a', 'b']],
name: undefined
}
const d = cloner(c)
d.name = "Super"
d.values[2].flag = false
d.values[3] = [6,7,8]
console.log({ c, d })
It's recursive and self-contained, all the functions needed are defined in the function cloner().
In this snippet we are handling Array and Object types if you want to add more handlers you can add specify handlers like Date and clone it like new Date(v.getTime())
For me Array and Object are the types that I use the most in my implementations.
I was watching some videos about prototype pollution in JavaScript, it seems that my code should be affected but testing code don't work:
// jQuery mock
var $ = {
isArray: (x) => x instanceof Array
};
var Clone = {
clone_object: function(object) {
var tmp = {};
if (typeof object === 'object') {
if ($.isArray(object)) {
return this.clone_array(object);
} else if (object === null) {
return object;
} else {
for (var key in object) {
if ($.isArray(object[key])) {
tmp[key] = this.clone_array(object[key]);
} else if (typeof object[key] === 'object') {
tmp[key] = this.clone_object(object[key]);
} else {
tmp[key] = object[key];
}
}
}
}
return tmp;
},
clone_array: function(array) {
if (!is_function(Array.prototype.map)) {
throw new Error("Your browser don't support ES5 array map " +
'use es5-shim');
}
return array.slice(0).map(function(item) {
if (typeof item === 'object') {
return this.clone_object(item);
} else {
return item;
}
}.bind(this));
}
};
var clone = function(object) {
return Clone.clone_object(object);
};
var x = JSON.parse('{"__proto__": {"foo": 10}}');
console.log(x);
var y = clone(x);
console.log(y);
var z = {};
console.log(z.foo);
Is prototype pollution something that don't work anymore or there is issue with my code? If issue is with the code what would need to be changed to pollution happen when calling clone function?
I think I know what the problem is:
This is vulnerable:
({}).__proto__.x = 10
console.log(({}).x);
but this is not
var tmp = {}
var tmp2 = {x: 10};
tmp.__proto__ = tmp2;
because the first example mutates the existing __proto__ object, but the second example replaces the whole __proto__ property, but doesn't mutate the previous __proto__ object.
The code in the question creates a new prototype object. It does not modify the prototype of a created object.
I should suppose to flatten a object, to do this I use this function:
var flatter = function(ob){
var f = {};
for(var i in ob) {
if(typeof ob[i] == 'object') {
var newOb = flatter(ob[i]);
for(var x in newOb) {
f[i+'.'+x] = newOb[x];
}
}else{
f[i] = ob[i];
}
}
return f;
}
works fine. I am getting proper result to applying this object:
var ob = {
"address" : {
"details" : {
"first" : "siva",
"last" : "sankara",
"mam":["mam1","mam2"]
}
}
};
the result is :
reslut : Object {address.details.first: "siva", address.details.last: "sankara", address.details.mam.0: "mam1", address.details.mam.1: "mam2"}
But I am not able to understand the result how i am getting. I understand that, this is oriented with recursion and closure scope - But seaching google I am not get any clear tutorial or article.
Any one help me to understand this my step by step please?
Here is the live demo
Thanks In advance!
function flatter(ob){
'use strict';
var f = {}, //return this
key;
for(key in ob) { //for each key
if (ob.hasOwnProperty(key)) {
if(typeof ob[key] === 'object') { //if value is object
//flatten this object again. Assign result to newOb
var newOb = flatter(ob[key]);
for(var x in newOb) {
f[key + '.' + x] = newOb[x];
}
} else {
f[key] = ob[key];
}
}
}
return f;
}
you can translate this code in something like that
function flatter(ob){
'use strict';
var f = {}, //return this object
key;
for(key in ob) { //for each key
if (ob.hasOwnProperty(key)) {
if(typeof ob[key] === 'object') { //if value is object
var newOb = (function (ob) {
'use strict';
var f = {}, //return this object
key;
for(key in ob) { //for each key
if (ob.hasOwnProperty(key)) {
if(typeof ob[key] === 'object') { //if value is object
var newOb = flatter(ob[key]);
for(var x in newOb) {
f[key + '.' + x] = newOb[x];
}
} else {
f[key] = ob[key];
}
}
}
return f;
}(ob[key]));
for(var x in newOb) {
f[key + '.' + x] = newOb[x];
}
} else {
f[key] = ob[key];
}
}
}
return f;
}
main idea is that every function call can be substituted by body of this function.
Object itself is a recursive structure, because can content objects. If given
{
id: 12345,
name: 'John',
friends: [12346, 75645, 96768]
}
recursion is not needed. Object doesn't contain any objects, so it could be straigtened without additional function call (by the way it is flat). If given
{
id: 12345,
name: {
first: 'John',
last: 'Doe'
},
friends: [12346, 75645, 96768]
}
then object contains object as field. So you can use function flatter where function call is substituted with body of function. If given
{
id: 12345,
name: {
first: 'Helen',
last: {
beforeMarriage: 'Dobsky',
afterMarriage: 'Bobsky'
}
},
friends: [12346, 75645, 96768]
}
then one can't do without 3 function calls. So you can copy body of function three times. But, object can have [infinitely] very deep structure. So number of nested bodies of function is unknown. So, instead of nesting body of function into function recursive call is used.
Recursive function should have at least one exit point to avoid infinite recursion
return f;
in our case. This exit point can be reached because number of fields in object is finite. This is not the only way to solve task. As object looks like tree (a kind of) recursion could be substituted with stack, which keeps complex fields and after processing simple fields return back to stack of objects and treat them in a loop.
Stack implementation. Not beautiful, but works)
function iflatter(input) {
'use strict';
var f = {}, //return this object
key,
stack = [],
ob,
prefix,
name;
stack.push(["", input]);
while (stack.length != 0) {
[prefix, ob] = stack.pop();
for(key in ob) { //for each key
if (ob.hasOwnProperty(key)) {
if (prefix !== "") {
name = prefix + "." + key;
} else {
name = key;
}
if(typeof ob[key] === 'object') {
stack.push([name, ob[key]]);
} else {
f[name] = ob[key];
}
}
}
}
return f;
}
You are passing an Object to flatter(). When the Object has a property that has a value that is an Object itself, it passes that Object to flatter() again, or recursively. See if(typeof ob[i] == 'object')? That means that if ob has property i and its value (gotten as ob[i]) is an Object. Note var flatter = function(){} is equivalent to function flatter(){}.
In python you can have a defaultdict(int) which stores int as values. And if you try to do a 'get' on a key which is not present in the dictionary you get zero as default value.
Can you do the same in javascript/jquery
You can build one using a JavaScript Proxy
var defaultDict = new Proxy({}, {
get: (target, name) => name in target ? target[name] : 0
})
This lets you use the same syntax as normal objects when accessing properties.
defaultDict.a = 1
console.log(defaultDict.a) // 1
console.log(defaultDict.b) // 0
To clean it up a bit, you can wrap this in a constructor function, or perhaps use the class syntax.
class DefaultDict {
constructor(defaultVal) {
return new Proxy({}, {
get: (target, name) => name in target ? target[name] : defaultVal
})
}
}
const counts = new DefaultDict(0)
console.log(counts.c) // 0
EDIT: The above implementation only works well with primitives. It should handle objects too by taking a constructor function for the default value. Here is an implementation that should work with primitives and constructor functions alike.
class DefaultDict {
constructor(defaultInit) {
return new Proxy({}, {
get: (target, name) => name in target ?
target[name] :
(target[name] = typeof defaultInit === 'function' ?
new defaultInit().valueOf() :
defaultInit)
})
}
}
const counts = new DefaultDict(Number)
counts.c++
console.log(counts.c) // 1
const lists = new DefaultDict(Array)
lists.men.push('bob')
lists.women.push('alice')
console.log(lists.men) // ['bob']
console.log(lists.women) // ['alice']
console.log(lists.nonbinary) // []
Check out pycollections.js:
var collections = require('pycollections');
var dd = new collections.DefaultDict(function(){return 0});
console.log(dd.get('missing')); // 0
dd.setOneNewValue(987, function(currentValue) {
return currentValue + 1;
});
console.log(dd.items()); // [[987, 1], ['missing', 0]]
I don't think there is the equivalent but you can always write your own. The equivalent of a dictionary in javascript would be an object so you can write it like so
function defaultDict() {
this.get = function (key) {
if (this.hasOwnProperty(key)) {
return key;
} else {
return 0;
}
}
}
Then call it like so
var myDict = new defaultDict();
myDict[1] = 2;
myDict.get(1);
A quick dirty hack can be constructed using Proxy
function dict(factory, origin) {
return new Proxy({ ...origin }, {
get(dict, key) {
// Ensure that "missed" keys are set into
// The dictionary with default values
if (!dict.hasOwnProperty(key)) {
dict[key] = factory()
}
return dict[key]
}
})
}
So the following code:
n = dict(Number, [[0, 1], [1, 2], [2, 4]])
// Zero is the default value mapped into 3
assert(n[3] == 0)
// The key must be present after calling factory
assert(Object.keys(n).length == 4)
Proxies definitely make the syntax most Python-like, and there's a library called defaultdict2 that offers what seems like a pretty crisp and thorough proxy-based implementation that supports nested/recursive defaultdicts, something I really value and am missing in the other answers so far in this thread.
That said, I tend to prefer keeping JS a bit more "vanilla"/"native" using a function-based approach like this proof-of-concept:
class DefaultMap {
constructor(defaultFn) {
this.defaultFn = defaultFn;
this.root = new Map();
}
put(...keys) {
let map = this.root;
for (const key of keys.slice(0, -1)) {
map.has(key) || map.set(key, new Map());
map = map.get(key);
}
const key = keys[keys.length-1];
map.has(key) || map.set(key, this.defaultFn());
return {
set: setterFn => map.set(key, setterFn(map.get(key))),
mutate: mutationFn => mutationFn(map.get(key)),
};
}
get(...keys) {
let map = this.root;
for (const key of keys) {
map = map?.get(key);
}
return map;
}
}
// Try it:
const dm = new DefaultMap(() => []);
dm.put("foo").mutate(v => v.push(1, 2, 3));
dm.put("foo").mutate(v => v.push(4, 5));
console.log(dm.get("foo")); // [1, 2, 3, 4, 5]
dm.put("bar", "baz").mutate(v => v.push("a", "b"));
console.log(dm.get("bar", "baz")); // ["a", "b"]
dm.put("bar", "baz").set(v => 42);
console.log(dm.get("bar", "baz")); // 42
dm.put("bar", "baz").set(v => v + 1);
console.log(dm.get("bar", "baz")); // 43
The constructor of DefaultMap accepts a function that returns a default value for leaf nodes. The basic operations for the structure are put and get, the latter of which is self-explanatory. put generates a chain of nested keys and returns a pair of functions that let you mutate or set the leaf node at the end of these keys. Accessing .root gives you the underlying Map structure.
Feel free to leave a comment if I've overlooked any bugs or miss useful features and I'll toss it in.
Inspired by #Andy Carlson's answer, here's an implementation that works in a slightly more Pythonic way:
class DefaultDict {
constructor(defaultVal) {
return new Proxy(
{},
{
get: (target, name) => {
if (name == '__dict__') {
return target;
} else if (name in target) {
return target[name];
} else {
target[name] = defaultVal;
return defaultVal;
}
},
}
);
}
}
Basically, it also lets you retrieve all the gotten and set values of the "target", similar to how collections.defaultdict works in Python. This allows us to do things like:
const myDict = new DefaultDict(0);
myDict['a'] += 1;
myDict['b'] += 2;
myDict['c'] += 3;
myDict['whatever'];
console.log(myDict.__dict__);
// {'a': 1, 'b': 2, 'c': 3, 'whatever': 0}
To add to Andy Carlson's answer
If you default dict an array, you'll get a toJSON field in the resulting object. You can get rid of it by deconstructing to a new object.
const dd = new DefaultDict(Array);
//...populate the dict
return {...dd};
The original answer does not seem to work on the nested cases. I made some modifications to make it work:
class DefaultDict {
constructor(defaultInit) {
this.original = defaultInit;
return new Proxy({}, {
get: function (target, name) {
if (name in target) {
return target[name];
} else {
if (typeof defaultInit === "function") {
target[name] = new defaultInit().valueOf();
} else if (typeof defaultInit === "object") {
if (typeof defaultInit.original !== "undefined") {
target[name] = new DefaultDict(defaultInit.original);
} else {
target[name] = JSON.parse(JSON.stringify(defaultInit));
}
} else {
target[name] = defaultInit;
}
return target[name];
}
}
});
}
}
var a = new DefaultDict(Array);
a["banana"].push("ya");
var b = new DefaultDict(new DefaultDict(Array));
b["orange"]["apple"].push("yo");
var c = new DefaultDict(Number);
c["banana"] = 1;
var d = new DefaultDict([2]);
d["banana"].push(1);
var e = new DefaultDict(new DefaultDict(2));
e["orange"]["apple"] = 3;
var f = new DefaultDict(1);
f["banana"] = 2;
The difference is that if defaultInit is an object, we need to return a deep copy of the object, instead of the original one.
Assume I have an object:
var obj = {
foo:"bar",
fizz:"buzz"
};
I need to access a property of that object dynamically like so:
var objSetter = function(prop,val){
obj[prop] = val;
}
No problems there, except for that prop needs to be case insensitive in case the property name is passed into the function as, say, Foo instead of foo.
So how can I point to an object's property by name without regard to case? I would like to avoid iterating the entire object if possible.
Try this:
var myObject = { "mIxeDCaSEKeY": "value" };
var searchKey = 'mixedCaseKey';
var asLowercase = searchKey.toLowerCase();
myObject[Object.keys(myObject).find(key => key.toLowerCase() === asLowercase)];
You can alternatively already provide the searchKey in lowercase.
If you want it as a function:
/**
* #param {Object} object
* #param {string} key
* #return {any} value
*/
function getParameterCaseInsensitive(object, key) {
const asLowercase = key.toLowerCase();
return object[Object.keys(object)
.find(k => k.toLowerCase() === asLowercase)
];
}
If the key can't be found, then it'll return undefined, just like normal.
If you need to support older browsers, then you can use filter instead:
function getParameterCaseInsensitive(object, key) {
const asLowercase = key.toLowercase();
return object[Object.keys(object).filter(function(k) {
return k.toLowerCase() === asLowercase;
})[0]];
}
I suggest using the polyfills for Object.keys() and Array.filter() if you need even older support.
Note: If you want to also check non-enumerable keys, use Object.getOwnPropertyNames() instead of Object.keys().
Nerdy Note: This assumes your Object doesn't have a key undefined (eg: const foo = {[undefined]: 'bar'};). That's just weird.
Compare all the properties of obj with prop.
var objSetter = function(prop,val){
prop = (prop + "").toLowerCase();
for(var p in obj){
if(obj.hasOwnProperty(p) && prop == (p+ "").toLowerCase()){
obj[p] = val;
break;
}
}
}
For this, I prefer using the prototype over a standalone function just for ease of use and expressiveness. I just don't like funneling objects into functions if I don't have to.
Also, while the accepted answer works, I wanted a more comprehensive solution for both getting and setting that would behave as much like the native dot notation or bracket notation as possible.
With that in mind, I created a couple prototype functions for setting/getting an object property without regard to case. You have to remember to be VERY responsible when adding to the Object prototype. Especially when using JQuery and other libraries. Object.defineProperty() with enumerable set to false was used specifically to avoid conflict with JQuery. I also didn't bother naming the functions anything that indicates they are case-insensitive, but you certainly could. I like shorter names.
Here's the getter:
Object.defineProperty(Object.prototype, "getProp", {
value: function (prop) {
var key,self = this;
for (key in self) {
if (key.toLowerCase() == prop.toLowerCase()) {
return self[key];
}
}
},
//this keeps jquery happy
enumerable: false
});
Here's the setter:
Object.defineProperty(Object.prototype, "setProp", {
value: function (prop, val) {
var key,self = this;
var found = false;
if (Object.keys(self).length > 0) {
for (key in self) {
if (key.toLowerCase() == prop.toLowerCase()) {
//set existing property
found = true;
self[key] = val;
break;
}
}
}
if (!found) {
//if the property was not found, create it
self[prop] = val;
}
return val;
},
//this keeps jquery happy
enumerable: false
});
Now that we've created those functions, our code is super clean and concise and just works.
Case-insensitive getting:
var obj = {foo: 'bar', camelCase: 'humpy'}
obj.getProp("FOO"); //returns 'bar'
obj.getProp("fOO"); //returns 'bar'
obj.getProp("CAMELCASE"); //returns 'humpy'
obj.getProp("CamelCase"); //returns 'humpy'
Case-insensitive setting:
var obj = {foo: 'bar', camelCase: 'humpy'}
obj.setProp('CAmelCasE', 'super humpy'); //sets prop 'camelCase' to 'super humpy'
obj.setProp('newProp', 'newval'); //creates prop 'newProp' and sets val to 'newval'
obj.setProp('NewProp', 'anotherval'); //sets prop 'newProp' to 'anotherval'
Yet another variation on those already presented which pushes the iteration down into the Underscore/Lodash findKey function:
var _ = require('underscore');
var getProp = function (obj, name) {
var realName = _.findKey(obj, function (value, key) {
return key.toLowerCase() === name.toLowerCase();
});
return obj[realName];
};
For example:
var obj = { aa: 1, bB: 2, Cc: 3, DD: 4 };
getProp(obj, 'aa'); // 1
getProp(obj, 'AA'); // 1
getProp(obj, 'bb'); // 2
getProp(obj, 'BB'); // 2
getProp(obj, 'cc'); // 3
getProp(obj, 'CC'); // 3
getProp(obj, 'dd'); // 4
getProp(obj, 'DD'); // 4
getProp(obj, 'EE'); // undefined
This answer requires ES6.
const x = { 'aB': 1, 'X-Total-Count': 10, y3: 2 }
console.log(x[Object.keys(x).find(key=>{return key.match(/^ab$/i)})])
console.log(x[Object.keys(x).find(key=>{return key.match(/^x-total-count$/i)})])
console.log(x[Object.keys(x).find(key=>{return key.match(/^y3$/i)})])
It seems to me like a good candidate for Proxy with traps to convert string keys to either upper case or lower case and behaving like a regular object.
This works with either notation: dots or braquets
Here is the code:
'use strict';
function noCasePropObj(obj)
{
var handler =
{
get: function(target, key)
{
//console.log("key: " + key.toString());
if (typeof key == "string")
{
var uKey = key.toUpperCase();
if ((key != uKey) && (key in target))
return target[key];
return target[uKey];
}
return target[key];
},
set: function(target, key, value)
{
if (typeof key == "string")
{
var uKey = key.toUpperCase();
if ((key != uKey) && (key in target))
target[key] = value;
target[uKey] = value;
}
else
target[key] = value;
},
deleteProperty: function(target, key)
{
if (typeof key == "string")
{
var uKey = key.toUpperCase();
if ((key != uKey) && (key in target))
delete target[key];
if (uKey in target)
delete target[uKey];
}
else
delete target[key];
},
};
function checkAtomic(value)
{
if (typeof value == "object")
return new noCasePropObj(value); // recursive call only for Objects
return value;
}
var newObj;
if (typeof obj == "object")
{
newObj = new Proxy({}, handler);
// traverse the Original object converting string keys to upper case
for (var key in obj)
{
if (typeof key == "string")
{
var objKey = key.toUpperCase();
if (!(key in newObj))
newObj[objKey] = checkAtomic(obj[key]);
}
}
}
else if (Array.isArray(obj))
{
// in an array of objects convert to upper case string keys within each row
newObj = new Array();
for (var i = 0; i < obj.length; i++)
newObj[i] = checkAtomic(obj[i]);
}
return newObj; // object with upper cased keys
}
// Use Sample:
var b = {Name: "Enrique", last: "Alamo", AdDrEsS: {Street: "1233 Main Street", CITY: "Somewhere", zip: 33333}};
console.log("Original: " + JSON.stringify(b)); // Original: {"Name":"Enrique","last":"Alamo","AdDrEsS":{"Street":"1233 Main Street","CITY":"Somewhere","zip":33333}}
var t = noCasePropObj(b);
console.log(JSON.stringify(t)); // {"NAME":"Enrique","LAST":"Alamo","ADDRESS":{"STREET":"1233 Main Street","CITY":"Somewhere","ZIP":33333}}
console.log('.NaMe:' + t.NaMe); // .NaMe:Enrique
console.log('["naME"]:' + t["naME"]); // ["naME"]:Enrique
console.log('.ADDreSS["CitY"]:' + t.ADDreSS["CitY"]); // .ADDreSS["CitY"]:Somewhere
console.log('check:' + JSON.stringify(Object.getOwnPropertyNames(t))); // check:["NAME","LAST","ADDRESS"]
console.log('check2:' + JSON.stringify(Object.getOwnPropertyNames(t['AddresS']))); // check2:["STREET","CITY","ZIP"]
You could do this in order to "normalize" prop
var normalizedProp = prop.toLowerCase();
obj[normalizedProp] = val;
const getPropertyNoCase = (obj, prop) => obj[Object.keys(obj).find(key => key.toLowerCase() === prop.toLowerCase() )];
or
const getPropertyNoCase = (obj, prop) => {
const lowerProp = prop.toLowerCase(obj[Object.keys(obj).find(key => key.toLowerCase() === prop.toLowerCase() )];
}
The ES6 example posted by #nilloc is incorrect and will break in use.
Here is a working example:
const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[Object.keys(x).reduce((result,key)=>{
if (!result) {
return key.match(/x-total-count/i)
} else {
return result;
}
},null)]);
or better yet, it should return undefined if the key doesn't exist:
const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[Object.keys(x).reduce((result,key)=>{
if (!result) {
return key.match(/x-total-count/i) || undefined
} else {
return result;
}
},undefined)]);
One consideration is that the above example will return the last matching key in the object if there are multiple keys that match.
Here is an example with the code made into a function:
/**
* #param {Object} object
* #param {string} key
* #return {string||undefined} value || undefined
*/
function getKeyCase(obj,key) {
const re = new RegExp(key,"i");
return Object.keys(obj).reduce((result,key)=>{
if (!result) {
return key.match(re) || undefined
} else {
return result;
}
},undefined);
const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[getKeyCase(x,"x-total-count")]);
Its really sad that the iteration can't be skipped as it seems. For me what is acceptable but may not be for everyone is to shape the object one time via iteration and then use it in regular hashmap fashion.
const hashmap = {
'FOO': 'foo as in function programming',
'bar': 'bar is in baz',
};
const shapedmap = Object.entries(hashmap).reduce(
(acc, [key, val]) => (acc[key.toUpperCase()] = val, acc), {}
);
for (const term of ['foo', 'bar', 'baz']) {
const match = shapedmap[term.toUpperCase()]
match && console.log('awesome, we got the term.', match);
};
Even if it just one time lookup has to be performed, it shouldn't less performant as any other iteration solution since after 1 pass, the lookup speed is constant. (I guess).
This is an old question, but it was the first one I found.
As #ZachSmith says, you can use a Proxy.
Here's some example code:
function lowercase(oldKey) {
// Check that it's a string.
return typeof oldKey === 'string' ? oldKey.toLowerCase() : oldKey;
}
const propertiesMap = new Map(
Object.keys(obj).map(propKey => [lowercase(propKey), obj[propKey]])
);
const caseInsensitiveGetHandler = {
get: function(target, property, receiver) {
return propertiesMap.get(lowercase(property));
}
};
obj = new Proxy(obj, caseInsensitiveGetHandler);
For my use case, I only needed to proxy the object's getter, but you may need to implement more of the Proxy methods.
There is no need for any iteration. Since prop might not be a string, it should be coerced to a string first where appropriate since that's what objects do natively. A simple getter function is:
function objGetter(prop) {
return obj[String(prop).toLowerCase()];
}
If there is a requirement is to restring access to own properties:
function objGetter(prop) {
prop = String(prop).toLowerCase();
if (obj.hasOwnProperty(prop)) {
return obj.prop;
}
}
and a setter:
function objSetter(prop, val) {
obj[String(prop).toLowerCase()] = val;
}
Heres a very simple code to do this
Assuming that data is the array of objects like
data=[{"A":"bc","B":"nn"}]
var data=data.reduce(function(prev, curr) {
var cc = curr; // current value
var K = Object.keys(cc); // get all keys
var n = {};
for (var i = 0; i < K.length; i++) {
var key = K[i];//get hte key
n[key.toLowerCase()] = cc[key] // convert to lowercase and assign
}
prev.push(n) // push to array
return prev;
}, [])
Output will be
data=[{"a":"bc","b":"nn"}]
You might only need to do case-insensitive matching (usually expensive because of object iteration) IF a case-sensitive match (cheap and quick) fails.
Say you have:
var your_object = { "Chicago" : 'hi' , "deTroiT" : 'word' , "atlanta" : 'get r dun' } ;
And you have, for whatever reason, the_value, Detroit:
if( your_object.hasOwnProperty( the_value ) )
{
// do what you need to do here
}
else
{ // since the case-sensitive match did not succeed,
// ... Now try a the more-expensive case-insensitive matching
for( let lvs_prop in your_object )
{ if( the_value.toLowerCase() == lvs_prop.toLowerCase() )
{
// do what you need to do here
break ;
} ;
}
} ;
why would we do it that complicated when we simply can make it all lower case:
var your_object = {
"chickago" : 'hi' ,
"detroit" : 'word',
"atlanta" : 'get r dun',
GetName: function (status) {
return this[status].name;
} };
to call it: your_object.GetName(your_var.toLowerCase());
Another simple way:
function getVal(obj, prop){
var val;
prop = (prop + "").toLowerCase();
for(var p in obj){
if(obj.hasOwnProperty(p) && prop == (p+ "").toLowerCase()){
val = obj[p]
break;
}
}
return val;
}
Use it like this:
var obj = {
foo:"bar",
fizz:"buzz"
};
getVal(obj,"FoO") -> returns "bar"
Here is a nice recursive function that allows you to traverse a javascript object in a case-insensitive way:
let testObject = {'a': {'B': {'cC': [1,2,3]}}}
let testSeq = ['a','b','cc']
function keySequence(o, kseq) {
if(kseq.length==0){ return o; }
let validKeys = Object.keys(o).filter(k=>k.toLowerCase()==kseq[0].toLowerCase());
if(validKeys.length==0) { return `Incorrect Key: ${kseq[0]}` }
return keySequence(o[validKeys[0]], kseq.slice(1))
}
keySequence(testObject, testSeq); //returns [1,2,3]
This will convert everything to lowercase, but in a bind this could help if you are not concerned with retaining case.
var somedata = {
"MixEdCase": 1234
}
var temp = JSON.parse(JSON.stringify(somedata).toLowerCase());
console.log(temp.mixedcase);
// or
console.log(temp["mixedcase"]);
So, you will need to get the object key that matches the case of the existing object, then use this to do your object update.
const obj = {
foo:"bar",
fizz:"buzz"
};
// to get obj.foo or obj.FOO or obj.foO returning "bar"
// create regex expression of case insensitive version of the key string
const regex=passedKey=> new RegExp(`^${passedKey}$`,'gi');
// find the key that matches the string you are passing
const formattedKey=passedKey=>Object.keys(obj).find(key=>regex(passedKey).test(key));
formattedKey('Foo'); // returns foo
formattedKey('FoO'); // returns foo
// consequently you can can use it like wise
obj[formattedKey('Foo')] // returns bar
obj[formattedKey('FoO')] // returns bar
obj[formattedKey('foo')] // returns bar