How Do I Create Variable Object Keys Of Object Keys Of Objects? - javascript

Okay, so I have a search results array of objects where one of the object properties value (show value) matches a search. The structure of this array is as follows and may contain any number of different objects:
results = [
{
day: value,
time: value,
show: value,
sid: value,
network: value,
title: value,
ep: value,
link: value,
}
];
I am trying to consolidate all the results into one large object, merging any days or times that have the same value. However, I cannot simply look at each day or time value independently. For example, I need to retain 9:00 pm on Monday if there is a 9:00 pm on Tuesday as well.
To do this I am trying to create a new object structure like so:
for ( var i=0; i<results.length; i++ ) {
var uniqtime = results[i]["time"];
var uniqshow = results[i].show;
uniqresults[results[i].day] = {
uniqtime: {
uniqshow: {
sid: results[i].sid,
network: results[i].network,
title: results[i]["title"],
ep: results[i].ep,
link: results[i]["link"]
}
}
};
}
but obviously this won't work since the variable sub-object key names are treated as strings.
If I instead try to create the variable sub-objects/keys like so:
for ( var i=0; i<obj.length; i++ ) {
uniqresults[results[i].day] = {};
uniqresults[results[i].day][results[i]["time"]] = {};
uniqresults[results[i].day][results[i]["time"]][results[i].show] = {
sid: obj[i].sid,
network: results[i].network,
title: results[i]["title"],
ep: results[i].ep,
link: results[i]["link"]
};
}
I can indeed create the proper key names but I am forced to declare an empty object to define each key (uniqresults[obj[i].day] = {} & uniqresults[obj[i].day][obj[i]["time"]] = {}). If I don't declare like this it won't let me insert the other sub-keys/values that I need to. However, declaring like this doesn't allow me to merge my results arrays correctly since I am essentially emptying out the sub-key names each time I read a new result object!
Maybe I am making this more complicated than it should be. Maybe there is an easier way or a way underscore or jquery could simplify my task at hand. Regardless, I am quite desperate for a proper solution at the moment.

It seems like you could use a conditional check when redifining those objects.
var day, time;
for ( var i=0; i<obj.length; i++ ) {
// Instantiate the day reference if it doesn't exist
day = uniqresults[results[i].day] = uniqresults[results[i].day] || {};
// Instantiate the time reference if it doesn't exist
time = day[results[i].time] = day[results[i].time] || {};
time[results[i].show] = {
sid: obj[i].sid,
network: results[i].network,
title: results[i]["title"],
ep: results[i].ep,
link: results[i]["link"]
};
}
Cheers!

I don't know whether there's a neater solution to the wider problem, but the issue of overwriting the sub-objects each time can be solved by checking if they already exist before creating them.
A relatively compact and idiomatic way of doing this in JS is using the || operator, which unlike most languages returns the argument which evaluated to true, not simply a boolean true:
uniqresults[results[i].day] = uniqresults[results[i].day] || {};
The first time through, uniqresults[results[i].day] will be undefined, so evaluate to false, so {} will be assigned; subsequently, however, it will be an object, which evaluates to true, so will simply assign the variable to itself, leaving it unchanged.

Create an empty object only if the object's key does not exist:
if(!(results[i].day in uniqresults)){
uniqresults[results[i].day] = {};
}
And for sub keys so on.

Related

Setting value of key in nested object happening incorrectly using a for loop and if statement

Here is an example that you can type in your console. Super new to Javascript. The example is reproducible by opening a new tab and typing it out in a console (The JSX Fiddle's console feature is in beta, so I'm not sure if it can be trusted)
let clothing = ['card0', 'card1', 'card2', 'card3'];
let timers = {}
let timerObj = {"startTime": null, "pauseTime": null, "elapsedTime": null, "hasSubmitted": false} //Nested object I want for all indices, will manipulate 0th index alone inside for loop
for (var i = 0; i < clothing.length; i++) {
timers[i] = timerObj
if (i == 0) {
timers[i]["startTime"] = Date.now();
}
}
console.log(timers)
What I'm intending to do is, for the 0th index alone, set the timers[0]["startTime"] as Date.now(), and for the rest, let the startTime be null as defined in the timerObj.
Strangely, after running the for loop, I see that for all i, the Date.now() has been set. I understand that Javascript objects are mutable, but why is why are all indices being set to Date.now()?
I looked at other Javascript related Object questions related to a concept call "freezing", not sure I have my basics right.
EDIT: I think this is related the object reference being altered..
var clothing = ['card0', 'card1', 'card2', 'card3'];
var timers = {}
var timerObj = {"startTime": null, "pauseTime": null, "elapsedTime": null, "hasSubmitted": false} //Nested object I want for all indices, will manipulate 0th index alone inside for loop
for (var i = 0; i < clothing.length; i++) {
timers[i] = Object.assign({}, timerObj)
if (i == 0) {
timers[i]["startTime"] = Date.now();
}
}
console.log(timers)
You can refer this for more information on this topic.
You have to clone your object. There are multiple ways to clone. One would be spread operator(...). Like below:
let clothing = ['card0', 'card1', 'card2', 'card3'];
let timers = {}
let timerObj = {"startTime": null, "pauseTime": null, "elapsedTime": null, "hasSubmitted": false}
clothing.forEach((val, i)=>{
timers[i] = {...timerObj};
if(i==0){
timers[i].startTime = Date.now()
}
});
console.log(timers);
Javascript does not copy objects. It passes references around, so when you assign timers[i] = timerObj once, then you assign Date.now() once , this value goes to your single timeorObj. All subsequent assignments of timerObj to timers[i] for all i refer to the single timerObj you defined.
To fix this force a copy: timers[i] = JSON.parse(JSON.stringify(timerObj));
This will serialize your clean timerObj to a JSON string, then convert it back to a new javascript object and then assign the new object to timers[i].
This way, you end up with copies of timerObj in each slot of your timers array.

Alternate/better ways to initialize JavaScript object that needs multiple static values?

I have a JavaScript object with some static attribute values, dynamic attribute values and methods. Each time I need one of these objects, I will need 10 of them. Each of the 10 objects gets initialized by a dedicated object literal. That happens under 3 different contexts of a user doing something on a data entry form. User actions can cause the contexts to happen in any order, any number of times, but the same 10 objects will always be created in each context. By "same" I mean the static values for a "no_matl" object will be identical each time a "no_matl" object is created ... only a few dynamic attribute values (field value, previous value, date/time, context ID) are different for each context.
Is there a smarter way to do the initialization currently done with the const object literal? Originally I passed a bunch of params to the constructor and initialized the static attributes from those. The object literal approach seemed cleaner. Maybe there's a better way?
// object literals used to initialize a each of the 10
// different type objects.
const FIELD_NOMATERIAL = {
DispName: 'No Material',
DbName: 'NO_MATERIAL',
TrueVal: 'Yes',
InitVal: '',
DispWhenSet: 'yes',
DispWhenNotSet: ''
};
const FIELD_CPCAT = { ... same attributes, different values ...};
const FIELD_HCN = { ... same attributes, different values ...};
// ... 7 more like this ...
// context 1
var no_matl = new MyField(FIELD_NOMATERIAL),
cpcap = new MyField(FIELD_CPCAT),
hcn = new MyField(FIELD_HCN) .... 7 more like this
// object definition
function MyField() {
if (arguments.length == 1 && typeof(arguments[0]) === 'object' ) {
this.DispName = arguments[0].DispName ;
this.DbName = arguments[0].DbName ;
// .... etc for rest of static attributes ...
}
}
Sounds like what you want is a copy of the original object that can change values without changing the original. Try this:
const FIELD_NOMATERIAL = {
DispName: 'No Material',
DbName: 'NO_MATERIAL',
TrueVal: 'Yes',
InitVal: '',
DispWhenSet: 'yes',
DispWhenNotSet: ''
};
function getFreshCopy(original) {
return Object.assign({}, original);
}
var no_matl = getFreshCopy(FIELD_NOMATERIAL);
Using Object.assign({}, obj) will create a new copy that can be changed without the original values changing. no_matl can be adjusted and FIELD_NOMATERIAL remains in its original state.
Note that const means the variable cannot be assigned a new value. It does not mean that the contents of the object cannot be changed. That means the following is true:
const noChange = { a: 7 };
noChange.a = 8; // this is fine because 'a' is allowed to change
noChange = "hello"; // this gives TypeError: Assignment to constant variable.

Generic 2D hash in JavaScript?

In other languages it is possible to create a generic 2D hash. I know creating 2d hashes is possible in javascript as well as explained here, but I can't seem to find a generic way to achieve this.
As an example of what I am looking for. In Ruby you can do this:
2dhash = Hash.new{|h, k| h[k] = Hash.new }
puts 2dhash["test"]["yes"]
#=> nil
2dhash[1][2] = "hello"
puts 2dhash[1][2]
#=> "hello"
Notice that I have not initialized the second level of hash, it happens automatically.
Is it possible to somehow achieve the same in javascript? Specifically, a way to make a 2d hash without initializing the first level of hash (or hard-coding it to be even more specific). The 2dhash will be used dynamically, so I have no clue what the first level will be.
Looks like a nice data structure excercise, let me try :D
function Hash() {
this.hash = {};
}
Hash.prototype.set = function(val) {
var paths = Array.prototype.slice.call(arguments, 1) // all levels
var path = paths.shift() // first level
var hashed = this.hash[path]
if (paths.length) {
// still have deeper levels
if (!(hashed instanceof Hash)) {
hashed = this.hash[path] = new Hash()
}
Hash.prototype.set.apply(hashed, [val].concat(paths))
} else {
// last level
this.hash[path] = val
}
}
Hash.prototype.get = function() {
var paths = Array.prototype.slice.call(arguments, 0) // all levels
var path = paths.shift() // first level
var hashed = this.hash[path]
if (paths.length) {
// still have deeper levels
return Hash.prototype.get.apply(hashed, paths)
} else {
// last level
return hashed
}
}
Now, let's see if it works:
var trytry = new Hash()
trytry.set('the value to store', 'key1', 'key2')
trytry.get('key1') // Hash{key2: 'the value to store'}
trytry.get('key1', 'key2') // 'the value to store'
Hooray it works!
It also works for even deeper levels:
trytry.set('the value to store', 'key1', 'key2','key3', 'key4')
trytry.get('key1', 'key2','key3') // Hash{key4: 'the value to store'}
However, a disadvantage of this approach is that you have to use instance methods get and set, rather than native object literal getter/setter.
It's still incomplete. For production environment, we need to do more, e.g. methods and properties like contains, size, etc.
If you initialize the first level of the hash with objects, then you can reference the second level without typeErrors, even if the data was not defined before.
Example:
var _2dhash = {a: {}, b: {}, c:{}}
//Note you cannot start variable names with numbers in js
_2dhash['a']['missingElement'];
// > undefined
It works because you're accessing undefined properties of defined objects. If you try to access through a missing top-level object, ie.
_2dhash['d']['whatever'];
You will get a TypeError, because _2dhash.d was not defined, and the second lookup fails, trying to read the 'whatever' property of undefined.

Javascript function that takes variable number of arguments - changes their value - and then returns them

I am new to Javascript (and programming in general) and have been searching for a way to change the value of an arbitrary number of aguments using a Javascript function.
The answer here (JavaScript variable number of arguments to function) was quite helpful. I was able to use it to create the two of the functions I need, but I'm having trouble with the third.
Basically I would like to pass a variable number of objects (primitive or more complex) into a function and have the function change the value of each object.
var pants = true;
var house = true;
var hair = {/* lots of stuff */};
var onFire = function() {
for (var i = 0; i < arguments.length; i++) {
arguments[i] = false;
}
};
onFire(pants, house, hair);
Console outputs:
>pants;
true
>house;
true
>hair;
Object
How can I formulate this function so the the result is:
>pants;
false
>house;
false
>hair;
false
Any help would be greatly appreciated!
Edit:
To clarify things - I am trying to create a reusable helper function that changes the value of any object passed in to false instead of typing:
var a1 = false;
var a2 = false;
...
var a+n = false;
If I use mathec's method - is it possible to 'parse' object so that it's properties overwrite the global variables of the same name, or am I stuck with typing it out explicitly each time?
var object = {
pants: {/* lots of stuff */},
house: {/* lots of stuff */},
hair: {/* lots of stuff */}
};
function modifyObject(obj) {
obj.pants = false;
obj.house = false;
obj.hair = false;
}
function someMagic(object){
// wand waves...
// rabbit exits hat....
}
Console Output:
>pants;
false
>house;
false
>hair;
false
When you pass variables in JavaScript you're passing a copy of a reference to a value if it's an object, but if it's a primitive (like a true/false) you're copying the actual value. This basically means, if you pass in an object and modify one of the object's properties, you'll be modifying the original object. But if you pass in a primitive like true/false, you'll just be modifying the copy, and not the original value. So if your goal is to modify the original values, one option is to store those values in an object and pass in the object instead. For example:
var object = {
pants: true,
house: true,
hair: {}
};
function modifyObject(obj) {
obj.pants = true;
obj.house = true;
obj.hair = true;
}
If you want to modify an arbitrary number of arguments, just remember that if you're passing in true/false you're copying those values. So if you change them inside the function you won't be modifying the original values.
A quick way to remember this is if you ever pass an object or an array, you're modifying the actual object. Anything else, you're modifying a copy.
Edit
So interpreting what you want to do literally, you could write this:
var a = true,
b = true;
/* pass in variable names */
function makeFalse() {
var i, len;
for (i = 0, len = arguments.length; i < len; ++i) {
window[arguments[i]] = false;
}
}
makeFalse("a", "b");
But I can't think of a good reason to do this :-). I would use configuration objects to store flags and state variables, not global variables.
Unfortunately, this is not directly possible.
Javascript is a pass-by-value language, and in the case of object types, its a pass by value by reference (at least thats how Ive seen it referred to)
Is JavaScript a pass-by-reference or pass-by-value language?
goes pretty in depth to it, and https://stackoverflow.com/a/3638034/1093982 in particular has a great answer.
here is a jsfiddle demonstrating the example in the above answer:
http://jsfiddle.net/hjPHJ/
note how anything assigning to a parameter is not carried into the scope above.
You could try JavaScript's typeof() function, which should return "object", and then treat it differently in your code. As for changing the value of an object, I think you actually want to change the value of a property, but I am not sure I understand your question well enough.
To change an object property, you would do something like this.
hair.color = 'red';
If you want to unset the object, you can do this.
delete window.some_var;
Javascript passes each argument as a reference by value, so when you change the value of a passed in reference it modifies the copied value.
However it does have something else which is very powerful and thats closure. In your example onFire already has access to pants, house, and hair through the closure scope and can modify them directly.
When you call a function in javascript, you are passing values and not references to the original object. There are a couple of ways that you can do what you want, but not directly by passing each value to change to the function.
First you can pass an array:
function onFire(arr) {
for (var i = 0; i < arr.length; i++) {
arr[i] = false;
}
}
arr = [true, true, {}];
onFire(arr); // arr[0] through arr[2] are now false
Another would be to pass an object, whose properties can be modified, you just can't change the value of the variable you pass as an argument:
function onFire(obj) {
obj.pants = false;
obj.house = false;
obj.hair = false;
}
onFire( { pants: true, house: true, hair: { } } );
// obj.pants, obj.house and obj.hair are now false
Neither of these accomplishes just what you want, but if I knew the reason for doing what you are asking there might be a better way...

pushing javascript objects to arrays

I have a loop goes through an array of objects MyArrayOfObjects and then pushes the objects to a new array like this:
var NewArray = new Array();
for (i = 0; i < MyArrayOfObjects.length; i++) {
TempObject = null;
TempObject = new Object();
// I have logic that copies certain properties but not others
// but overall it looks like this:
TempObject.prop1 = MyArrayOfObjects[i].prop1;
TempObject.prop2 = MyArrayOfObjects[i].prop2;
NewArray.push(TempObject);
}
As I loop through MyArrayOfObjects, I clear the TempObject and create a new one each time. Does NewArray contain the objects that I'm copying or just a reference to the objects copied and that then become deleted as the loop iterates?
Thanks.
It contains references to the objects themselves.
This code shows that concept in action (notice that changing the object after pushing it into the array changes the object in the array as well):
var ray = new Array();
var obj = { foo: 123 };
ray.push(obj);
obj.foo = 321;
alert(ray[0].foo);
> var NewArray = new Array();
It is generally considered better to use an array literal to create an array. Variable names starting with a capital letter are, but convention, used for constructors. Using "new" at the start of a variable name can easily slip to become "new Array", and the name should reflect its purpose, so something like the following might be better:
var objectArray = [];
.
> for (i = 0; i < MyArrayOfObjects.length; i++) {
You should always declare variables, especially counters as undeclared variables are made properties of the global object (effectively global variables) when they are first assigned a value. Also, it is considered better to store the length of the array than get it in each iteration:
for (var i = 0, iLen = MyArrayOfObjects.length; i < iLen; i++) {
.
> TempObject = null;
> TempObject = new Object();
Again, declare variables. Assigning a value of null serves no useful purpose when you're going to assign some other value immediately afterward. Just do the second assignment (and use a literal):
var TempObject = {};
.
> // I have logic that copies certain properties but not others
> // but overall it looks like this:
>
> TempObject.prop1 = MyArrayOfObjects[i].prop1;
> TempObject.prop2 = MyArrayOfObjects[i].prop2;
>
> NewArray.push(TempObject);
At this point, TempObject and NewArray[NewArray.length - 1] both reference the same object.
> }
As I loop through MyArrayOfObjects, I clear the TempObject and create
a new one each time.
There is no need to "clear" the object, just assign a new value to the variable. In javascript, all variables have a value that might be a primitive (e.g. string, number) or a reference to an object (e.g. Object, Array, Number, String)
Does NewArray contain the objects that I'm
copying or just a reference to the objects copied and that then become
deleted as the loop iterates?
It contains references to the new objects created on each iteration.
As variables hold references to objects, assigning a new value to the variable doesn't do anything to the object. When an object is no longer referenced by any variable or object property, it is made available for garbage collection and may be removed automatically at some later time when garbage collection runs.
Using map or its jquery counterpart might be a more idiomatic way of doing this. For example:
var oldArray = [
{ prop1: 1, prop2: 10 },
{ prop1: 2, prop2: 20 },
{ prop1: 3, prop2: 30 }
]
var newArray = $.map(oldArray, function(oldObj) {
return { newProp: oldObj.prop1 }
})
console.log(newArray)

Categories

Resources