Array with object keys in JavaScript - javascript

How can I create an array with keys as objects (instances of a class)?
I am trying to do something like:
const parent = {};
while (child !== undefined) {
for (const element of list) {
parent[element] = child;
}
child = child.next;
}
This is basically the idea; the code works if element is a string, but it doesn't work correctly if the element is an object.

If you're in an ES2015 Environment you can use a Map
It would look like this:
let parent = new Map();
while (child !== undefined) {
for (const element of list) {
parent.set(element, child);
}
child = child.next;
}
You can run the below proof in this codepen
let parent = new Map();
const KEY1 = {};
parent.set(KEY1, 'hello');
console.log(parent.get(KEY1)); // hello
const KEY2 = {};
parent.set(KEY2, 'world');
console.log(parent.get(KEY2));
parent.set('est', {a: 'a'});
console.log(parent.get('est'));
Or see it in action as a stack snippet
(function($) {
const ELEMENTS = {
$h1: $('#hello'),
$container: $('#container')
};
let parent = new Map();
const KEY1 = {};
parent.set(KEY1, 'hello');
console.log(parent.get(KEY1)); // hello
const KEY2 = {};
parent.set(KEY2, 'world');
console.log(parent.get(KEY2));
parent.set('est', {
a: 'a'
});
console.log(parent.get('est'));
/** original code from question w Map **/
var list = []; // to prevent error
let parenta = new Map();
let child;
while (child !== undefined) {
for (const element of list) {
parenta.set(element, child);
}
child = child.next;
}
})(jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1 class="hello"></h1>
<div class="container"></div>

You can't. .toString() is called on anything passed to an object as a key before it's used as a key. This is part of the Javascript language specification.
You can however make Symbols object keys if you want, but Symbols only take strings as a parameter.

This does not work as the method toString() will be called return something like [object Object] for all the items.
Property names can only be strings, but they can be any string... You could just turn the object into a string, using JSON.stringify(element) but you won't be able to use the key value like the object, since it will only be a representation of it...
Another solution (maybe better?) is to have a function on your class that create a string representation of the object... Let's say your object is coming from the database, you could use the record ID. You could use some random string too for what it is worth.
I'd be able to give you a better solution if I knew why you want to store the object as the key to start with.

Related

How can I clone HTML Nodes with custom properties?

I am working on a Editor and want to clone a HTML Node with custom properties using JavaScript.
I only found a way using setAttribute() but it converts my custom attribute into a string:
// Using custom attributes
var html = document.createElement("div");
var obj = {test: 123,html: html};
html.obj = obj;
var cloned = html.cloneNode(true);
console.log(cloned.obj); // returns null
// Using setAttribute
var html = document.createElement("div");
var obj = {test: 123, html: html};
html.setAttribute("obj") = obj;
var cloned = html.cloneNode(true);
console.log(cloned.getAttribute("obj")); // returns "[object Object]"
How do I clone the html element with the object?
Attributes in HTML are string values, not JavaScript Objects and JavaScript Properties. The cloneNode operation only clones HTML intrinsics and not anything you add on top, it is not the same thing as a deep object copy.
You will need to do it manually:
function cloneCustomNode(node) {
var clone node.cloneNode(); // the deep=true parameter is not fully supported, so I'm not using it
clone.obj = node.obj; // this will copy the reference to the object, it will not perform a deep-copy clone of the 'obj' object
return clone;
}
This can be generalised to copy any custom JavaScript properties from one object to another, excluding those already defined in the default (defaultNode).
var defaultNode = document.createElement("div");
function cloneNodeWithAdditionalProperties(node) {
var clone node.cloneNode();
for(var propertyName in node) {
if( !( propertyName in genericNode ) ) {
clone[ propertyName ] = node[ propertyName ];
}
}
return clone;
}
cloneNodeWithAdditionalProperties will run in O( n ) time because the if( x in y ) operation is a hashtable lookup with O( 1 ) complexity (where n is the number of properties).
You could use a property of HTMLElement.dataset however the api only allows storing strings which would mean using JSON.stringify() while setting and JSON.parse() while getting arrays or objects
var html = document.createElement("div");
var obj = {test: 123,html: html};
html.dataset.obj = JSON.stringify(obj);
var cloned = html.cloneNode(true);
console.log(JSON.parse(cloned.dataset.obj));
One approach is to use Object.keys() to iterate over the node (which is an Object) and apply the discovered properties, and their property-values, to the created node, for example:
// named function to follow DRY principles;
// n: DOM node, the node to be cloned:
function customClone(n) {
// creating a temporary element to hold the
// cloned node:
let temp = n.cloneNode(true);
// Using Object.keys() to obtain an Array of
// properties of the Object which are not
// inherited from its prototype, and then
// using Array.prototype.forEach() to iterate
// over that array of properties:
Object.keys(n).forEach(
// using an Arrow function, here 'property' is
// the current property of the Array of properties
// over which we're iterating, and then we
// explicitly assign the property-value of the
// node that was cloned to the property-value of
// that same property on the clone:
property => temp[property] = n[property]
);
// returning the clone to the calling context:
return temp;
}
let html = document.createElement("div"),
obj = {
test: 123,
html: html
};
html.obj = obj;
let cloned = customClone(html);
console.log(cloned.obj);
function customClone(n) {
let temp = n.cloneNode(true);
Object.keys(n).forEach(
property => temp[property] = n[property]
);
return temp;
}
let html = document.createElement("div"),
obj = {
test: 123,
html: html
};
html.obj = obj;
let cloned = customClone(html);
console.log(cloned.obj);
JS Fiddle demo.
References:
Array.prototype.forEach().
Arrow functions.
let statement.
Node.cloneNode.
Object.keys().
Extending the accepted answer, I created a snippet that deep clones all the childNodes as well.
// Make a generic element to compare default properties
const DIV = document.createElement('div');
function fullClone(n: Node) {
// Clone the element without DEEP, so we can manually clone the child nodes
const temp = n.cloneNode();
// Loop through all the properties
for (let prop in n) {
// Skip if the property also exists in the div element
if (prop in DIV) {
continue;
}
// We try/catch in case the property is readonly
try {
// Copy the value
temp[prop] = n[prop];
} catch {
// readOnly prop
}
}
// Remove any childNodes left (text nodes)
temp.childNodes.forEach(c => temp.removeChild(c));
// Deep clone all the childNodes
n.childNodes.forEach(c => temp.appendChild(fullClone(c)));
return temp;
}

Convert JSON to JavaScript Object of Type X

I know all about JSON.stringify or JSON.parse in the sense that one serializes an object and one deserializes the string back into an object. This is great!
However, I have the following situation:
var i = new MyMagicalObject();
var oi = JSON.parse(JSON.stringify(i));
console.log(i.numFields()); // this is fine
console.log(oi.numFields()); // this throws since Object has no method 'numFields'
Basically, I'd like to treat oi as an instance of "MyMagicalObject" since that's what it is.
I'm sure there's some magic about setting the prototype on oi or something, but I'm fairly new to JavaScript. Any help would be appreciated.
You can't "store" JavaScript functions in JSON strings.
The only data types that can be stored in JSON are:
Number
String
Boolean
Array
Object
null
(source)
Anything that isn't one of those types, gets ignored:
function Test(){
this.foo = function(){
return 'bar';
}
this.theAnswer = '42';
}
var t = new Test();
alert(t.foo());
alert(JSON.stringify(t))
Your problem could be easily solved by redesigning your MyMagicalObject class. Here is an example of JSON-friendly class:
function MyMagicalObject(props) {
this.props = props || {};
}
MyMagicalObject.prototype.get = function(key) {
return this.props[key];
};
MyMagicalObject.prototype.set = function(key, val) {
this.props[key] = val;
return this;
};
MyMagicalObject.prototype.toJSON = function() {
return this.props;
};
MyMagicalObject.prototype.numFields = function() {
return Object.keys(this.props).length;
};
This realization follows two rules:
It's constructor accepts JSON representation as a first argument.
It provides toJSON method to tell JS engine how to convert its instance to JSON.
Check the following example:
var obj = new MyMagicalObject();
obj.set('foo', 42).set('bar', 'baz');
alert(obj.numFields()); // 2
var str = JSON.stringify(obj);
var obj2 = new MyMagicalObject(JSON.parse(str));
alert(obj2.numFields()); // 2
You can create a new MyMagicalObject() and then overwrite its properties with the one from oi.
var t = new MyMagicalObject();
for(var k in oi) t[k]=oi[k];
That should do the trick. If you have a more complex object (with more than 1 dimension), search for a copy function that deep copies all properties.
Add oi.prototype = MyMagicalObject.prototype; after line 3.
or
create a new object and copy the properties:
var oi2 = new MyMagicalObject();
for (var p in oi) {
if (oi.hasOwnProperty(p)) {
oi2[p] = oi[p]
}
}
console.log(oi2.numFields());

Initialize a JavaScript object "tree" to any depth, nested objects

Essentially my I am trying to initialize a JavaScript object and have it contain empty objects with a single key. For example:
getOject('one.two.three')
Would result in the object:
{one:{two:{three:''}}}
As far as I can tell, you can't initialize with dynamic key names unless you use array notation
root[dynamicKey] = 'some variable';
so I need to loop through and based on the number of args initialize each one then assign it's value but the syntax doesn't seem to let me do this in any way that I know of.
So, if it were not a loop it would be like this:
jsonifiedForm[rootKey] = {};
jsonifiedForm[rootKey][childKeys[0]] = {};
jsonifiedForm[rootKey][childKeys[0]][childKeys[1]] = $input.val();
I can't think of a way to do this, I am not typically a JS guy so it might be something simple but I couldn't find anything on Google or Stack Overflow
Thank you in advance!
This function should be what you're looking for.
function getOject(str) {
// this turns the string into an array = 'one.two.three' becomes ['one', 'two', 'three']
var arr = str.split('.');
// this will be our final object
var obj = {};
// this is the current level of the object - in the first iteration we will add the "one" object here
var curobj = obj;
var i = 0;
// we loop until the next-to-last element because we want the last element ("three") to contain an empty string instead of an empty object
while (i < (arr.length-1)) {
// add a new level to the object and set the curobj to the new level
curobj[arr[i]] = {};
curobj = curobj[arr[i++]];
}
// finally, we append the empty string to the final object
curobj[arr[i]] = '';
return obj;
}
Because JavaScript references values in variables instead of copying them "into" variables, we can make our initial value, then make a reference to it which we'll move around as we delve down in:
var getOject = function (k, s) {
// initialize our value for return
var o = {},
// get a reference to that object
r = o,
i;
// we'll allow for a string or an array to be passed as keys,
//and an optional sepeartor which we'll default to `.` if not given
if (typeof k === 'string') {
k = k.split(s || '.');
}
// do we have an array now?
if (k && k.length) {
//iterate it
for (i = 0; i < k.length; i += 1) {
// set a property on the referenced object
r[k[i]] = {};
// point the reference to the new level
r = r[k[i]];
}
}
// send back the object
return o;
}
console.log(getOject('one.two.three'));
console.log(getOject('four|five|six', '|'));
r points to the same thing that o does, initially, and as we move the reference (r) deeper into o and write to it, we're building out o as we go.
The two console.log() calls at the end output the following:
Also notice I let you pass in an array to start with if you feel like it, and made the separator a parameter so that you're not stuck with .

Memory efficient way to make an object empty in javascript?

Basically what I want to do is, to use single object everytime after make it empty when my purpose is served.
For array in javascript, I used to write arr.length=0 to make any array empty, instead of pointing it to different memory location. is there any way through which I can empty an object in javascript ?
Scenario is:
var obj = {};
obj["name"]="Aman";
obj["country"]="India";
console.log(obj); // output is { name: 'Aman', country: 'India' }
Can I reused this obj object after removing its content ? if so how ?
The only way I can think of would be to loop over the object and delete each property in turn.
var obj = {};
obj["name"]="Aman";
obj["country"]="India";
for (var prop in obj) {
// Possibly with a hasOwnProperty test, depending on how empty you want it to be
delete obj[prop];
}
console.log(obj);
Obviously, if you aren't dealing with multiple references to the object, you can just overwrite it with a new one.
var obj = {};
obj["name"]="Aman";
obj["country"]="India";
obj = {};
console.log(obj);
for (var member in myObject) {
if ( myObject.hasOwnProperty(member) ) {
delete myObject[member];
}
}
use ECMAScript 6 Map:
var obj = new Map();
obj.set("name", "Aman");
obj.set("country", "India");
obj.clear();

Dynamically create sub-objects and arrays when needed

I have an object created from JSON via AJAX from the server. The object has several sub-objects in an array, e.g.:
obj.subObj1[0].value="abc";
obj.subObj1[1].value="abc";
obj.subObj2[0].value="abc";
Now I want to set some values in this object but I dont know if they already exist.
obj.subObj1[0].value="new Value"; // No Problem
obj.subObj2[1].value="new Value2"; // Problem because obj.subObj2[1] is no Object.
I would need to do obj.subObj2[1]={} first.
Because I have this problem very often I am looking for method to automate this. A method or class which does automatically create the needed object (or array if I use an integer).
It should be able to handle an infinite depth of such sub-objects. Like this:
var obj = TheObject();
obj.sub1.sub2[10].sub3[1].sub4='value';
Now automatically all needed sub-objects and arrays should be created.
Cannot really guarantee anything about cross-browser compatibility, but how about trying this on for size (works in Chrome):
// Safely sets value property of index of an array of an object.
function setObj(obj, subObjName, index, val) {
// Ensure the object exists
if (typeof obj == 'undefined') {
obj = new Object();
}
// Ensure the array property exists
if (typeof obj[subObjName] == 'undefined') {
obj[subObjName] = new Array();
}
// Ensure the array properties index exists
if (typeof obj[subObjName][index] == 'undefined') {
obj[subObjName][index] = {};
}
// Set the value
obj[subObjName][index].value = val;
// Return the object
return obj;
}
Example use:
<script type="text/javascript">
var obj;
obj = setObj(obj, "something", 1, "val");
setObj(obj, "something", 0, "someValue");
alert(obj.something[1].value);
alert(obj.something[0].value);
</script>
If you can assume that the referenced item in the array will be either undefined or an object it simplifies things. Of course the simple (non-automatic) way would be something like this:
if (!obj.subObj2[1]) obj.subObj2[1] = {};
obj.subObj2[1].value = "new Value2";
A not-very generic function to do it for you would be:
function setArrayObjectProp(arr, index, prop, val) {
if (!arr[index])
arr[index] = {};
arr[index][prop] = val;
}
// called as
setArrayObjectProp(obj.subObj2, 1, "value", "new Value2");
heloo
try testing the type of the array item first if its not object then equal it to the new object format {value:"new Value2"}
if(typeof(obj.subObj2[1])!='object')
{
obj.subObj2[1] = {value:"new Value2"};
}
else
{
obj.subObj2[1].value = "new Value2";
}

Categories