A couple of days ago I was having fun with some js, when I came to the question if I could automate object nesting. Of course I'm still a newby so I haven't gotten too far.
But what I got is this:
var a = {};
var stso = ""; storing the second object
function sto(b) { // start the object
a[b] = {};
stso = b;
}
function nmo(...objs) { // nesting more object
console.log(objs[0]);
if(objs.length) { // checking to see that I have at least one variable before proceding
for(i = 0; i < objs.length; i++) { // looping through arguments
a[stso][objs[i]] = {}; // and now I would have to repeat one more more for lever for every argument, meaning, the deeper I want to go into the object, the more nested for loops I have to make.
}
}
}
sto("b");
nmo("c");
a.b.c = "Happy ending!";
console.log(a.b.c); // It seems we still dont have a happy ending
// and as a second example
sto("b");
nmo("c", "d", "e", "f", "g");
a.b.c.d.e.f.g = "Another happy ending!";
console.log(a.b.c.d.e.f.g); // Our second happy ending was also unhappy...
In summary, you define the second object in one function, you define as many objects as you want in your second function in order.
How could I achieve this with my current structure?
If i understand you correctly, you can do something like this:
var createNestedObjects = function( obj ) {
//get all the passed arguments
var args = Array.prototype.slice.call(arguments);
//start with i = 1, as we want to skip obj in the arguments
for( var i = 1; i < args.length; i++ ) {
obj = obj[ args[i] ] = obj[ args[i] ] || {};
}
};
var a = {};
createNestedObjects( a, "b", "c", "d", "e", "f" );
a.b.c.d.e.f = "happy ending";
console.log(a.b.c.d.e.f); //logs "happy ending"
explanation line 3
Your requirement is to pass as many strings as required to the function to create any size of nested objects you would like.
However, as you can see, the function only has one parameter: obj.
The cool thing is that javascript allows you to pass even more parameters and you are still able to access them using the arguments object. The arguments object is available in all functions. The arguments object is similar to an array, but not quite the same as it is an object in it's own, if you log it in this case it will display:
Arguments [{}, "b", "c", "d", "e", "f"] (6)
We can't loop through the agruments object using a for loop, so on line 3 it's converted to an array first.
arguments object reference
explanation inside the loop
There are two interesting parts in this line. Javascript allows you to assign multiple variables at once as the assignment operator assigns a value to its left operand based on the value of its right operand
var a, b;
a = b = 10; //both a and b are set to 10
Using || operator is used here to set a default value ( in this case {} )if the value on the left is undefined. Which is for example also handy to set default values
function setDelay ( delay ){
delay = delay || 60
return delay
}
setDelay( ) //no parameter is passed, delay is set to 60
setDelay( 120 ) 120 is passed and delay is set to 120
In this case the line
obj = obj[ args[i] ] = obj[ args[i] ] || {};
can be rewritten as:
var name = args[i]; // first loop args[i] is "b"
if(!obj[name]){ // a.b does not exist
obj[name] = {}; // a.b is set to {}
}
obj = obj[name]; // a is set to a.b
which checks if there is already an object with that name, if not it's created and set as obj so we can nest objects in the next loop.
I hope this clarifies the code
So, you want to convert values to a nested object? This can be done by doing something like this:
let values = ['b', 'c', 'd', 'e', 'f', 'g'];
let a = {};
// Every time we loop, we change the root
let root = a;
for (let value of values) {
// Set root to the value of the array and assign a new object
root = root[value] = {};
}
// Set the string
a.b.c.d.e.f.g = "Happy ending!";
console.log(a.b.c.d.e.f.g);
console.log(a);
It basically does this inside the loop:
a = a[b] = {};
a[b] = a[b][c] = {};
a[b][c] = a[b][c][d] = {};
a[b][c][d] = a[b][c][d][e] = {};
a[b][c][d][e] = a[b][c][d][e][f] = {};
a[b][c][d][e][f] = a[b][c][d][e][f][g] = {};
It creates a new key every loop and assigns an empty object to it. The next iteration creates a new key (with an empty object as value) inside the newly created object from the previous loop. This goes on until all values of the array have been assigned.
Related
Is there a way to index an object in JavaScript like a Metatable in Lua?
Like for example:
var obj = {a:"a",b:"b",properties:{c:"c",d:"d"}}
metatable(obj,obj.properties) // Make it so that if you try to index something that's not inside the object it will go to the parameter one
console.log(obj.a) // "a"
console.log(obj.c) // "c"
To LMD:
How do I do it for multiple objects? Like for example:
var objs = [
obj1 = {name:"Button";class:"button";properties:{text:"Press this"}]
]
for (i in objs){
metatable(objs[i],objs[i].properties)
}
console.log(objs.obj1.text) // "Press this"
Yes: JavaScript has prototypes. These aren't exactly the same as Lua but can be used for simple metatable indexing purposes. One way to achieve your example would be as follows:
const properties = {c: "c", d: "d"} // prototype
const obj = Object.create(properties) // create object with prototype
obj.a = "a"; obj.b = "b";
console.log(obj.a) // "a"
console.log(obj.c) // "c"
Or if you already have the objects given, as in your second example, you may want to use Object.setPrototypeOf(object, prototype), which is comparable to setmetatable(object, {__index = prototype}) in Lua:
const objs = [{name:"Button", class:"button", properties: {text:"Press this"}}]
for (const obj of objs) Object.setPrototypeOf(obj, obj.properties)
console.log(objs[0].text) // "Press this"
that is, the metatable function you've been searching for literally is Object.setPrototypeOf!
The function takes an input path like a.b.c and should output a nested structure json like:
{
a: {
b: {
c: {}
}
}
}
The algorithm using iterative style is:
function stringToObj(path, obj) {
var parts = path.split(".");
var part;
while ((part = parts.shift())) {
if (typeof obj[part] != "object") obj[part] = {};
obj = obj[part]; // line 6
}
}
Current usage:
let result = {};
stringToObj("a.b.c", result);
console.log(result); // outputs the json
JsFiddle
The problem:
It relies mutating the obj parameter on line 6.
I would like to not rely upon passing the result object, and rather create one inside the function. Doing so results in different results. A desired example usage:
const result = stringToObj("a.b.c"); // result should be the json
Context:
The exercise is for learning purpose. Main objective is understanding why removing obj and rewriting the function as per follows doesn't work as expected:
function stringToObj(path) {
var obj = {};
var parts = path.split(".");
var part;
while ((part = parts.shift())) {
if (typeof obj[part] != "object") obj[part] = {};
obj = obj[part]; // line 6
}
return obj;
}
After splitting by .s, you can use reduceRight to start at the last property, c, while taking an initial value of an empty object. Inside the callback, use a computed property to return a new object containing the old object at the property being iterated over:
const stringToObj = str => str.split('.').reduceRight(
(lastObj, prop) => ({ [prop]: lastObj }), {}
);
console.log(stringToObj('a.b.c'));
If you're not familiar with it, reduceRight is like reduce, except that it iterates starting from the last element in the array and going backwards, instead of starting from the first element of the array and going forwards. On each iteration, the callback is called, where the first argument (here, lastObj) is the value returned from the last iteration, and the second argument is the current item of the array being iterated over.
You can also reverse the array of properties and use reduce instead of reduceRight, which might be easier to understand at a glance, but it's a bit less elegant:
const stringToObj = str => str.split('.').reverse().reduce(
(lastObj, prop) => ({ [prop]: lastObj }), {}
);
console.log(stringToObj('a.b.c'));
Also, don't mix var and let. If you're going to use ES2015 syntax - which you should - consider always using const, and only use let when you must reassign. Never use var, it has too many gotchas to be worth using in modern code.
I think I've seen how to create a JSON object without first preparing it. This is how i prepare it:
obj = {
0:{
type:{}
},
1:{},
2:{}
};
Now I think I can insert a value like: obj.0.type = "type0"; But I'd like to create it while using it: obj['0']['type'] = "Type0";.
Is it possible, or do I need to prepare it? I'd like to create it "on the fly"!
EDIT
I'd like to create JS object "On the fly".
var obj = {};
obj.test = "test"; //One "layer" works fine.
obj.test.test = "test" //Two "layers" do not work... why?
obj = {
0:{
type:{}
},
1:{},
2:{}
};
Now i think i can insert value like: obj.0.type = "type0";
I guess you mean "assign" a value, not "insert". Anyway, no, you can't, at least not this way, because obj.0 is invalid syntax.
But I'd like to create it while using it: obj['0']['type'] = "Type0";
That's fine. But you need to understand you are overwriting the existing value of obj[0][type], which is an empty object ({}), with the string Type0. To put it another way, there is no requirement to provide an initialized value for a property such as type in order to assign to it. So the following would have worked equally well:
obj = {
0:{},
1:{},
2:{}
};
Now let's consider your second case:
var obj = {};
obj.test = "test"; //One "layer" works fine.
obj.test.test = "test" //Two "layers" do not work... why?
Think closely about what is happening. You are creating an empty obj. You can assign to any property on that object, without initializing that property. That is why the assignment to obj.test works. Then in your second assignment, you are attempting to set the test property of obj.test, which you just set to the string "test". Actually, this will work--because strings are objects that you can set properties on. But that's probably not what you want to do. You probably mean to say the previous, string value of obj.test is to be replaced by an object with its own property "test". To do that, you could either say
obj.test = { test: "test" };
Or
obj.test = {};
obj.test.test = "test";
You are creating a plain object in JavaScript and you need to define any internal attribute before using it.
So if you want to set to "Type0" an attribute type, inside an attribute 0 of an object obj, you cannot simply:
obj['0']['type'] = "Type0";
You get a "reference error". You need to initialize the object before using it:
var obj = {
0: {
type: ""
}
};
obj['0']['type'] = "Type0";
console.log(obj['0']['type']);
You could create your own function that takes key as string and value and creates and returns nested object. I used . as separator for object keys.
function create(key, value) {
var obj = {};
var ar = key.split('.');
ar.reduce(function(a, b, i) {
return (i != (ar.length - 1)) ? a[b] = {} : a[b] = value
}, obj)
return obj;
}
console.log(create('0.type', 'type0'))
console.log(create('lorem.ipsum.123', 'someValue'))
Is it necessary to create nested objects before using it?
Yes it is, at least the parent object must exist.
Example:
var object = {};
// need to assign object[0]['prop'] = 42;
create the first property with default
object[0] = object[0] || {};
then assign value
object[0]['prop'] = 42;
var object = {};
object[0] = object[0] || {};
object[0]['prop'] = 42;
console.log(object);
Create object with property names as array
function setValue(object, keys, value) {
var last = keys.pop();
keys.reduce(function (o, k) {
return o[k] = o[k] || {};
}, object)[last] = value;
}
var object = {};
setValue(object, [0, 'prop'], 42);
console.log(object);
I set two global variables:
var topList = {
a: {},
b: {},
c: {},
d: {},
e: {}
}
var compare = {
a: {},
b: {},
c: {},
d: {},
e: {}
}
I have a function which populates each of them, and then uses a for loop to swap out the a object within the compare variable. Then it calls a function to compare the new compare to topList, and returns the better of the two (thus setting topList as the better of the two:
function optimize(data){
var rawList = data.slice();
var aList = $.grep(rawList, function(e) { return e.position == "A" });
var bList = $.grep(rawList, function(e) { return e.position == "B" });
var cList = $.grep(rawList, function(e) { return e.position == "C" });
var dList = $.grep(rawList, function(e) { return e.position == "D" });
var eList = $.grep(rawList, function(e) { return e.position == "E" });
topList.a = aList[0];
topList.b = bList[0];
topList.c = cList[0];
topList.d = dList[0];
topList.e = eList[0];
compare = topList;
for (i = 0, len = aList.length; i < len; i++) {
compare.a = aList[i];
topList = best(topList, compare);
}
}
For some reason, it seems that when the line compare.a = aList[i]; is executed, it's not only swapping out the a object in the compare variable, but also the a object in the topList variable. As a result I'm always sending two identical lists through my "best" function, which makes it useless.
I'm new to this. Any help would be greatly appreciated!
In an attempt to explain simply, when you do:
var x = {};
You take an empty object and assign it to x.
If you then do:
var y = x;
You are taking the same object and assigning it to y as well.
From then, if you do...
y.foo = 'bar';
You will find that...
alert(x.foo); // bar (!)
This is called assignment by-reference, and it is what happens in JavaScript with objects (note that arrays are objects too, with predefined methods).
The opposite is assignment by-value, where the value is copied to the new variable.
So because you have this by-reference assignment, changes you make in one place will affect the other. You will need to use a copying function to get a new object, unrelated to the first, with the same value.
Because compare is a reference to topList.
You don't need to put
compare =topList;
Simply, this would work :
compare .a = aList[0];
compare .b = bList[0];
compare .c = cList[0];
compare .d = dList[0];
compare .e = eList[0];
I'm faced with a situation in JavaScript when I need to update an object via its pointer similar to ะก++ array of pointers to objects
Example code for my issue:
var foo = new Array();
var bar = function(){
this.test = 1;
foo.push(this); // push an object (or a copy of object?) but not pointer
};
var barInst = new bar(); // create new instance
// foo[0].test equals 1
barInst.test = 2;
// now barInst.test equals 2 but
// foo[0].test still equals 1 but 2 is needed
So, how can I solve this? Should I use a callback or something like this or there is an easy way to help me to avoid copying the object instead pushing the raw pointer into an array?
JS is pass-by-value, so your original assignment was this.test = the value of 1, in my example, it's this.test = the object pointed to by ptr, so when I change ptr this.test changes as well.
var foo = [],
ptr = {val: 1},
bar = function(){
this.test = ptr;
foo.push(this); // push an object (or a copy of object?) but not pointer
},
barInst = new bar(); // create new instance
// foo[0].test.val equals 1
ptr.val = 2;
// foo[0].test.val equals 2
Although if you thought that foo.push(this); was similar, it isn't. Since this is an object, the array will indeed contain "raw pointers" to objects, just like you want. You can prove this simply:
foo[0].test = 3;
// barInst.test === 3
Which shows that it is indeed a pointer to the object that was pushed onto the array
"create object method pointer"
Object.defineProperty(Object.prototype,'pointer',{
value:function(arr, val){
return eval(
"this['"+arr.join("']['")+"']"+
((val!==undefined)?("="+JSON.stringify(val)):"")
);
}
});
ex of use
var o={a:1,b:{b1:2,b2:3},c:[1,2,3]}, arr=['b','b2']
o.pointer(arr) // value 3
o.pointer(['c',0], "new_value" )