This is a fairly common question here in SO, and I've looked into quite a few of them before deciding to ask this question.
I have a function, hereby called CheckObjectConsistency which receives a single parameter, an object of the following syntax:
objEntry:
{
objCheck: anotherObject,
properties: [
{
//PropertyValue: (integer,string,double,whatever), //this won't work.
PropertyName: string,
ifDefined: function,
ifUndefined: function
}
,...
]
}
What this function does is... considering the given parameter is correctly designed, it gets the objCheck contained within it (var chk = objEntry.objCheck;), It then procedes to check if it contains the properties contained in this collection.
Like this
for(x=0;x<=properties.length;x++){
if(objCheck.hasOwnProperty(properties[x].PropertyName)){
properties[x].ifDefined();
}
else{
properties[x].ifUndefined();
}
What I want is... I want to bring it to yet another level of dynamicity: Given the propositions that IfDefined and IfUndefined are functions to be called, respectively, if the currently-pointed PropertyName exists, and otherwise, I want to call these functions while providing them, as parameters, the very objCheck.PropertyName's value, so that it can be treated before returning to the user.
I'll give a usage example:
I will feed this function an object I received from an external provider (say, a foreign JSON-returning-WebService) from which I know a few properties that may or may not be defined.
For example, this object can be either:
var userData1 = {
userID : 1
userName: "JoffreyBaratheon",
cargo: "King",
age: 12,
motherID : 2,
//fatherID: 5,--Not defined
Status: Alive
}
or
var userData2 = {
userID :
userName: "Gendry",
cargo: "Forger Apprentice",
//age: 35, -- Not Defined
//motherID: 4,-- Not Defined
fatherID: 3,
Status: Alive
}
My function will receive:
var objEntry=
{
objCheck: userData1,
properties: [
{
PropertyName: "age",
ifDefined: function(val){alert("He/she has an age defined, it's "+val+" !");},
ifUndefined: function(){alert("He/she does not have an age defined, so we're assuming 20.");},
},
{
PropertyName: "fatherID",
ifDefined: function(val){alert("He/she has a known father, his ID is "+val+" !");},
ifUndefined: function(){alert("Oh, phooey, we don't (blink!blink!) know who his father is!");},
}
]
}
CheckObjectConsistency(objEntry); // Will alert twice, saying that Joffrey's age is 12, and that his father is supposedly unknown.
ifDefined will only actually work if, instead of properties[x].ifDefined();, I somehow provide it with properties[x].ifDefined(PropertyValue);. And here, at last, lies my question.
Being inside the consistency-checking-function, I only know a given property's name if it's provided. Being inside it, I can't simply call it's value, since there is no such function as properties[x].ifUndefined(properties[x].GetValueFromProperty(properties[x].PropertyName)) ,... is there?
I'm sorry. Not being a native english speaker (I'm brazilian), I can't properly express my doubts in a short way, so I prefer to take my time writing a long text, in an (hopefully not wasted) attempt to make it clearer.
If, even so, my doubt is unclear, please let me know.
I think you're looking for the bracket notation here. It allows you to provide an arbitrary value as key to access the object. Also, you know its name. You have your properties object right?
objEntry.properties.forEach(function(property){
// Check if objCheck has a property with name given by PropertyName
if(!objEntry.objCheck.hasOwnProperty(property.PropertyName)){
// If it doesn't, call isUndefined
property.isUndefined();
} else {
// If it does, call isDefined and passing it the value
// Note the bracket notation, allowing us to provide an arbitrary key
// provided by a variable value to access objCheck which in this case is
// the value of PropertyName
property.isDefined(objEntry.objCheck[property.PropertyName]);
}
});
Oh yeah, forEach is a method of arrays which allows you to loop over them. You can still do the same with regular loops though.
Related
I am learning JavaScript based on Eloquent Javascript and during one of the chapters, came across this error. Not sure what I am doing wrong here. I am getting an error "Cannot read property 'indexOf' of undefined" against the code return journal.events.indexOf(event) != -1
Also, can someone explain how that line works? Isn't IndexOf supposed to return the first position of occurrence of the specified value (in this case, event)? But I see in the book that the line return journal.events.indexOf(event) != -1; returns either true or false.
var journal = [];
function addEntry(events, didITurnIntoASquirrel) {
journal.push({
events: events,
squirrel: didITurnIntoASquirrel
});
}
addEntry(["work", "touched tree", "pizza", "running",
"television"], false);
addEntry(["work", "ice cream", "cauliflower", "lasagna",
"touched tree", "brushed teeth"], false);
addEntry(["weekend", "cycling", "break", "peanuts",
"beer"], true);
function hasEvent(event, entry) {
return entry.events.indexOf(event) != -1;
}
console.log(hasEvent("pizza", journal));
In your sample code journal is an array
var journal = []; <--- Array
Therefore events should be accessed with an index like:
journal[0].events.indexOf(event)
^
|
|
Here you need to find the right index to get your events
I'm unsure how far along you are with learning javascript, so forgive me if some of this sounds condescending or obvious.
Let's break this down one step at a time. You begin with an empty array.
var journal = [];
console.log(journal); //[]
//it's defined. It's an empty Array.
By calling push on an array, you add something to the end of the array.
More on Array.push.
I don't like this example for beginners because it expects you to know already that you can define an object while you're passing it as an argument. This is done this way because you don't truly need a variable reference to an object that's only used once and is therefore a good way of reducing bloat in code. But verbosity is much better when teaching someone, imho.
//Wait, what am I pushing into the journal array?
journal.push({
events: events,
squirrel: didITurnIntoASquirrel
});
This should make more sense:
Create an object first. Then add that object to the "journal" array.
function addEntry(events, didITurnIntoASquirrel) {
var temporaryObject = {
events: events,
squirrel: didITurnIntoASquirrel
};
journal.push(temporaryObject);
}
Now journal is an array with an unnamed object at its first index.
1. console.log(journal); // [temporaryObject]
2. console.log(journal[0]); - //temporaryObject
The visibile difference is the lack of parens, but the difference is important.
On line 1 you have the array itself, on line 2 you have what's inside it (i.e. the object). You need to get the object (via the technique on line 2) before you can access properties of that object, such as "events" or "squirrel". Moving on.
addEntry(["work", "touched tree", "pizza", "running", "television"], false);
Next, we invoke the addEntry function. Same confusion here. I've rewritten it slightly to make the arguments more understandable.
var entry = ["work", "touched tree", "pizza", "running", "television"];
addEntry(entry, false);
//repeat 2 more times with different data
So first we define an array, then we pass it to the addEntry function. when the addEntry function runs (it will run right when we invoke it), the "entry" argument will be represented function as the "events" parameter (simple way: events = entry and didITurnIntoASquirrel = false). some notes on parameters vs arguments.
So you should be able to understand now that you're passing an array and a boolean to the addEntry function. That function creates an object based on those values referencing them via their parameters. That object is then added to the journal array.
What you end up with is 4 levels of depth. You have an array called journal, which has objects in it. Those objects have a property called events, which is a different array. That array has several strings inside it. To access the events array and use indexOf to see if it has a given string in it, you need to traverse that depth one level at a time.
//journal is the array, journal[0] is the object, journal[0].events is the property of that object
console.log(journal[0].events) //["work", "touched tree", "pizza", "running", "television"].
Note this is the same data that we originally put in the entry variable. This may seem unnecessarily complicated, but trust me when I say this type of structure is useful in real life when you need to manage data hierarchy or other logical relationships between "things" in Object Oriented programming.
Now, all the work we've done so far is to add to the journal array. We now want a function to see what's inside it. Why a function? So you don't have to rewrite the same code over and over.
function hasEvent(event, journal) {
return journal.events.indexOf(event) != -1;
}
By now I hope you can spot the error in this function. journal.events doesn't work, because journal is an array, not an object (you skipped a level, and your computer isn't smart enough to know what you mean) journal[0].events would work, because you are telling javascript ("from the journal array, I want the object in the first slot, and the events property of that object").
The simplest fix is to send journal[0] to the hasEvent function instead of journal. Beware, this will only check journals first index. Realistically you'd want a for loop inside the hasEvent function or wrapping the call to that function to check all indexes. For now we will hardcode them, since we know there are 3, but its not a good idea in real life, since later there may be more than 3 entries in the journal).
This funciton is returning the result of calling indexOf() (some number or -1) with -1. Let's again rewrite it so that it makes more sense.
New hasEvent function:
//I renamed the variable so it makes more sense what it really is. It's the object, not the journal array.
function hasEvent(event, journalEntry) {
var index = journalEntry.events.indexOf(event);
var result = (index != -1); //true if it was found, false if it wasn't found.
return result; //a boolean based on the above comparison.
}
//Ack! My kingdom for a "for loop". Don't worry about that right now.
console.log(hasEvent("pizza", journal[0]));
console.log(hasEvent("pizza", journal[1]));
console.log(hasEvent("pizza", journal[2]));
TL;DR
Here is a fiddle with working code:
http://jsfiddle.net/o8dg1ts6/1/
To answer your 2nd question:
"Isn't IndexOf supposed to return the first position of occurrence of the specified value"
Yes, and indexOf returns -1 if if the value is not found in the array.
So if the event is found, then the expression indexOf(event) != -1 will evaluate to true.
I want to pass following payload to the API
params[field1]: value1
params[field2]: value1
....
params[fieldN]: valueN
I have field and value coming from an object.
var params = {};
jQuery.each($scope.linkParams, function(a, b) {
params.params[a] = b; // returns undefined variable error
// I also tried other options but all result in one or another error
// Some doesn't result into an erro but doesn't get merged. See below merge requirement
});
I also wants to merge the above created object to another object with
jQuery.extend(extraParams, params);
How to achieve the rquired object?
Update
$scope.linkParams = {
field1: 'value1',
field2: 'value2',
....
};
You have two questions, so I'll address them one at a time.
(For a TL;DR, I emboldened the solution text. Hopefully the rest is worth the read, though.)
Object Serialization is Pretty Magical, but Not Quite That Magical
If I had a JS object that I instantiated like the following:
var cat = {
'meow': 'loud',
'type': 'Persian',
'sex': 'male'
}
then it is certainly true that you get attribute reference for free. That is, you can say something like cat.meow, and your runtime environment will make sense of that. However, JS will not automatically create properties of an object that you have referenced do not exist, unless you are referencing them to create them.
cat.health = 'meek' will work, but cat.ears[0] = 'pointy' will not.
var cat = {
'meow': 'loud',
'type': 'Persian',
'sex': 'male'
}
cat.health = 'meek'
alert(cat.health)
cat.ears[0] = 'pointy'
alert(cat.ears[0])
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You'll notice that the first alert happens and contains the expected value, but the second alert never comes. This is because the code fails on the line with cat.ears[0] = 'pointy', and it stops execution at that point.
This may seem to contradict what I just said, but look closely at what's happening. When we attempt to initialize the first element of cat.ears, we must reference the cat.ears property, which does not exist.
JS implementations won't assume that you want to create items up the chain eternally, which is likely by design -- if it didn't throw errors and instead just created any properties or objects that needed to exist in order for your program to by syntactically sound, many pieces of software would silently break when they failed to include required libraries. If you forgot to include JQuery, it'd just create a $, a JQuery variable, and all of the properties of those objects you reference in your code. It'd be a proper mess to debug.
In short, that's what you're -- probably accidentally -- assuming will work here. params.params is analogous to cat.ears in the above example. You may have some reason for doing this, but assuming you don't, your code should function if you simply change params.params[a] to params[a].
JQuery.extend()
Assuming that extraParams is a valid array/object, the code you have written will work once params doesn't break your code anymore, however: do note that this will modify your extraParams object. If you want a new object to contain both params and extraParams, write something more like:
var args = $.extend({}, params, extraParams)
That will modify an empty object and add in the contents of params and extraParams. See the JQuery documentation for more information.
Some manipulations and I was able to achieve the required results.
I am posting the code for further reference:
var d = {};
jQuery.each($scope.linkParams, function(a,b) {
a = "params[" + a + "]";
d[a] = b;
});
jQuery.extend(extraParams, d);
I'm trying to retrieve a nested object based on a nested nested id.
So my object is as follows
object = {
1: {
feature: {id:"1012"},
}
2: {
feature: {id:"3032"}
}
}
I have an id and I need to retrieve the corresponding object or to be more specific the object id. The object is a lot more complex but above shows the values that I need to retrieve.
I don't have much experience in JavaScript so am unsure how to achieve this. I've tried using Jquery's attribute selectors but have not been successful.
Any help would be appreciated.
Thanks.
if your "id" is mean like 1 or 2
do it like this:view it in JSFiddle
var obj = {
1: {
feature: {id:"1012"}
},
2: {
feature: {id:"3032"}
}
}
var getById = function(id){
return obj[id];
}
alert(getById(1).feature.id);
another way,if your id means like '1012','3032'
do it like this:view it in JSFiddle
my post about the Map in js
If I'm understanding the question correctly you are trying to use the id property of the object in each feature property to get the key (1, 2, etc) from object? So if you entered "1012" you would get back 1, if you entered "3032" you would get 2, etc?
If so this would do it:
var object = {
1: {
feature: {id:"1012"}
},
2: {
feature: {id:"3032"}
}
},
getIdByFeatureId = function (featureId) {
var id,
subObject;
// loop through each property of the object
for (id in object) {
// protect ourselves in case someone has tampered with Object.prototype
if (object.hasOwnProperty(id)) {
subObject = object[id];
if (subObject.feature.id === featureId) {
return id;
}
}
}
// none found? return null.
return null;
};
getIdByFeatureId("3032"); // returns 2
getIdByFeatureId("1012"); // returns 1
getIdByFeatureId("90210"); // returns null
You can play with the code in this fiddle.
Numbers stored as strings can be a pain, and often lead to confusion in how one need to call a function like this. One thing you might notice is I used the === strict equal operator. This only returns true if both values are exactly the same, including their type. It's good practice to use strict comparison operators unless you absolutely can't. It is also slightly faster since it doesn't have to coerce the values into a like type. But that means that you must pass a string into the function in order for it to match. You could use the non-strict equals == if you need it to be more flexible. If all of the feature ids are numeric (and none of them have leading zeros) and you have the ability to, I would change the feature ids to be actual numbers so you can keep it more intuitive by just passing in a number instead of a string representation of a number: getIdByFeatureId(3032); while keeping the strict comparison.
I have this code:
var string = {
nameString : "nameValue",
nameString2 : "nameValue2",
nameString3 : "nameValue3",
datathing : 0,
};
var data = {
data : 1,
dataNum2 : 2,
dataNum3 : 3,
dataNum4 : 4,
};
var thing = {
datathing1 : 10,
datathing2 : 20,
datathing3 : 30,
datathing4 : 40,
};
var object = {
object1 : string,
data1 : data,
thing1 : thing,
};
Why do neither of these means to access the data work:
alert("testReference= " + object['object1']['string']['nameString']);
alert("testReference= " + object.object1.string.nameString);
I cannot understand it, even though similar examples found below and textbooks state explicitly that they should work:
Accessing nested JavaScript objects with string key
Thanks in advance for any input!
I am currently constructing an object and passing it around, a 'for in' will bring up the values but a 'typeof' test or any other way I try and access will not work, either I will encounter an error (which breaks the program, I think) or I get 'undefined'....
One last thing if this gets solved, is it ok to nest a key that is the same name value as its parent, such as data.data - this leads to the possibility of further nesting such as data.data.data...
Let's look at what's wrong with each example, then take a look at the way that works right.
Example 1
object['object1']['string']['nameString']
We expect object['object1'] to return the object string, right? So lets simplify the big expression by replacing that part of it. That'll make it easier for us to understand.
So now we have string['string']['nameString'].
But string has no member called 'string', so string['string'] returns undefined.
And when you try to treat undefined as an object, you get an error!
Example 2
object.object1.string.nameString
We expect object.object1 returns the object string, right? So lets simplify the big expression by replacing that part of it. That'll make it easier for us to understand.
So now we have string.string.nameString.
But string has no member called 'string', so string.string returns undefined.
And when you try to treat undefined as an object, you get an error!.
What You Want
object.object1.nameString (or object['object1']['nameString'])
We expect object.object1 returns the object string, right? So lets simplify the big expression by replacing that part of it. That'll make it easier for us to understand.
So now we have string.nameString, and we expect that to return "nameValue".
And it does!
I have something similar to this:
...
meta: {
'orderby' : 'firstname',
'page' : 1,
'per' : 10
}
...
When I get send the request using ajax, part of my response contains some of this meta data. So I overwrite it with the new stuff. The server might send back something like:
meta: {
'page' : 1,
'per' : 10
}
The problem is that it overwrites the orderby key to be undefined. I don't want to have the server send back everything, how can I leave a key's value if the key isn't sent back?
As you've said you're using jQuery, you can use its extend function:
jQuery.extend(originalMeta, returnedMeta);
That will only overwrite properties in the target (originalMeta, in the above) with properties from the source (returnedMeta in the above) that actually exist. (No need to assign the result of the function to originalMeta, it's modified in place.)
It's also dead easy without relying on jQuery:
var name;
for (name in returnedMeta) {
if (returnedMeta.hasOwnProperty(name)) {
originalMeta[name] = returnedMeta[name];
}
}
That uses a for..in loop to loop through all (enumerable) properites on returnedMeta, filters out any it inherits from its prototype (it probably doesn't inherit any, but...), and for ones that exist copies the values into originalMeta.
You can use extend().
var meta = $.extend(meta, {
'page': 1,
'per': 10
});
T.J. Crowder notes that the first argument's object is modified, no need to return (though it will work as well).
The problem is that it overwrites the orderby key to be undefined
It doesn't overwrite the property, it overwrites the entire Object. When you access an Object's property that doesn't exist, you get undefined.