I want to confirm the behavior I am seeing regarding the this reference, and the ability to modify keys (not just values) in an object using the reviver function.
If I pass my reviver function using function(key,value {...} as opposed to using an arrow function (key, value) => {...}, the this reference seems to refer to the object being revived. This is true for sub-objects in the JSON as well. I am seeing this in node.js 8.x on the server, and in Chrome current on the client.
Understandably, if I pass the function as an arrow function, the calling context is preserved.
I am relying on this to add and delete some keys as I parse the JSON.
Can I rely on this behavior?
var aTestStr = '{"prop1": "this is prop 1",'
+'"prop2": {"prop2A": 25, "prop2B": 13, "prop2C": "This is 2-c"}'
+'}';
var aTestObj = JSON.parse(aTestStr, function(key, value) {
//at this point, this refers to the object being revived
//E.g., when key == 'prop1', this is an object with prop1 and prop2
//when key == prop2B, this is an object with prop2A, prop2B and prop2C
//So is this code reliable?
if (key == this.prop2B) {
//Do something, add a prop to this:
this.prop2BDif = 100 - this.prop2B;
}
});
Yes it is documented: JSON.parse documentation in the MDN
If a reviver is specified, the value computed by parsing is transformed before being returned. Specifically, the computed value and all its properties (beginning with the most nested properties and proceeding to the original value itself) are individually run through the reviver. Then it is called, with the object containing the property being processed as this, and with the property name as a string, and the property value as arguments.
Related
I am running an Angular app that pulls its data from a Web API service. The API returns the objects as JSON and the Angular service (via $http.get() ) returns them to the controller as an array of objects. Pretty normal stuff.
What I'd like to do is add a property to each of the returned objects called "Selected". This property is purely for GUI purposes (is the object selected or not in the UI) and doesn't need to be persisted in any way. I figured the easiest thing to do was loop through the returned array of objects and just add it. So my code looks like this:
function getData() {
myService.getData()
.then(function(data) {
$scope.myData = data.results;
// Add a "Selected" property to each object
$.each($scope.myData, function(item) {
item.Selected = false;
});
}
When it gets to the line that says, "item.Selected = false" it throw an error message, saying "Cannot assign to read-only property Selected".
It is unclear to me why "Selected" is read-only? I didn't know if maybe Angular does some funky object processing when it reads data? What am I doing wrong here? Or should I be approaching this a completely different way?
Note (I'd like to avoid having to make Selected a part of the original object, as it's not representative of anything related to that object).
to add property to an object use underscorejs,
"each _.each(list, iteratee, [context]) Alias: forEach
Iterates over a list of elements, yielding each in turn to an iteratee function. The iteratee is bound to the context object, if one is passed. Each invocation of iteratee is called with three arguments: (element, index, list). If list is a JavaScript object, iteratee's arguments will be (value, key, list). Returns the list for chaining."
"extend _.extend(destination, *sources)
Copy all of the properties in the source objects over to the destination object, and return the destination object. It's in-order, so the last source will override properties of the same name in previous arguments."
$scope.myData = data.results;
// Add a "Selected" property to each object
_.each($scope.myData, function(ent) {
_.extend(ent, {
Selected : false
});
});
Your debugger screenshot actually gives you a more useful error message than what you posted (emphasis mine):
Cannot assign to read only property 'Selected' of 0
This shows that instead of the object, you're getting a number as your item variable (0 in this case). Assigning properties to primitives in strict mode throws this "Cannot assign to read-only property" error. (Without strict mode, it just fails silently.)
As JcT pointed out in a comment, this is because $.each calls the function with 2 params passed, the index first, the value second. See the documentation of $.each:
callback
Type: Function( Integer indexInArray, Object value )
So even though you named your parameter item, it received the value of the current index instead. This means your code can be fixed by just adding this missing first parameter to your callback, like this:
function getData() {
myService.getData()
.then(function(data) {
$scope.myData = data.results;
// Add a "Selected" property to each object
$.each($scope.myData, function(index, item) { //index was added here!
item.Selected = false;
});
}
There is a Object generated by Node.js, it looks like this when I use console.log:
{ dataValues: { a: 1, b: 2 }, fn1: function(){}, fn2: function(){} }
when I use JSON.stringify, it return this string:
{"a":1,"b":1}
I checked the mozilla developer center and found this:
All symbol-keyed properties will be completely ignored, even when using the replacer function.
I think the 'dataValues' must be the 'symbol-keyed' property, but what does 'symbol-keyed' mean?
btw, I use the sequelizejs ORM lib to generate this object.
I found the reason finally in the same page:
If an object being stringified has a property named toJSON whose value is a function, then the toJSON method customizes JSON stringification behavior: instead of the object being serialized, the value returned by the toJSON method when called will be serialized.
It runs on browser normally.
Here is the jsfiddle to run it like I asked.
Code:
function test(data) {
for(var key in data){
this[key] = data[key];
}
}
test.prototype.toJSON = function(){
return this.dataValues;
}
var a = new test({dataValues: {a:1, b:2}});
console.log(a);//the instance
console.log(JSON.stringify(a));//{"a":1,"b":1}
Nah, the relevant part to your issue is this blurb:
If undefined, a function, or a symbol is encountered during conversion
it is either omitted (when it is found in an object) or censored to
null (when it is found in an array).
So in other words, if your JSON object contains functions, they are omitted during the JSON.stringify process.
"symbol-keyed" refers to a new primitive type introduced in ecmascript6. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof for more info.
This is an example of a "symbol-keyed" property:
{[Symbol("foo")]: "foo"}
Reference for JavaScript Symbol: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
I've been using localStorage and a question came to me:
Which is the advantage using setItem and getItem methods rather than:
SET ITEM : localStorage.myKey = "myValue";
GET ITEM : localStorgae.myKey --> returns "myValue"
Are they just helper methods then? Good practices?
Just about curiosity thanks.
HTML5 Storage is based on named key/value pairs. You store data based on a named key, then you can retrieve that data with the same key. The named key is a string. The data can be any type supported by JavaScript, including strings, Booleans, integers, or floats. However, the data is actually stored as a string. If you are storing and retrieving anything other than strings, you will need to use functions like parseInt() or parseFloat() to coerce your retrieved data into the expected JavaScript datatype.
interface Storage {
getter any getItem(in DOMString key);
setter creator void setItem(in DOMString key, in any data);
};
Calling setItem() with a named key that already exists will silently overwrite the previous value. Calling getItem() with a non-existent key will return null rather than throw an exception.
Like other JavaScript objects, you can treat the localStorage object as an associative array. Instead of using the getItem() and setItem() methods, you can simply use square brackets. For example, this snippet of code:
var foo = localStorage.getItem("bar");
// ...
localStorage.setItem("bar", foo);
…could be rewritten to use square bracket syntax instead:
var foo = localStorage["bar"];
// ...
localStorage["bar"] = foo;
Maybe this hope. :D
Reference: http://diveintohtml5.info/storage.html
set/getItem are better than property-access for the following reasons:
localStorage coerces all its input into strings, but you can overwrite the set/getItem methods to perform serialization and deserialization to support types other than strings:
var basicSetItem = localStorage.setItem;
localStorage.setItem = function(key, val) {
basicSetItem.call(localStorage, key, JSON.stringify(val));
}
var basicGetItem = localStorage.getItem;
localStorage.getItem = function(key) {
return JSON.parse(basicGetItem.call(localStorage, key));
}
You cannot achieve an equivalent effect for all storage property values using ECMAScript 5 APIs.
You cannot set the storage key length, and you cannot access the keys getItem, setItem, or removeItem without using function access:
localStoage.length = "foo"; // does not work
localStoage.setItem("length", "foo"); // works
var bar = localStoage.setItem; // gets the `setItem` function
var bar = localStorage.getItem("setItem"); // gets the value stored in the `setItem` key
This is my code:
<div class='a'>
<div class='b'>Test</div>
</div>
and
$(['.b']).each(function () {
console.log($('.a').find(this).text()); // Expecting to print "Test"
});
I expect the console.log to print Test, but it doesn't! Is this a jQuery bug?
There are a few problems here.
When you call jQuery with an array (like you're doing), jQuery expects it to be an array of DOM elements.
If you want to use $.each, use the static version that iterates over generic objects or arrays.
With both of those things said, there's stil an issue with using $.each with an array containing primitive values. The following code exhibits the same problem you were seeing:
$.each([".b"], function () {
console.log($('.a').find(this).text()); // Expecting to print "Test"
});
Inside the callback function for .each, this is a String object and not a string primitive. To understand this, we need to look at what .each is doing under the hood:
for (; i < length;) {
if (callback.apply(object[i++], args) === false) { // <----
break;
}
}
The important bit is the call to apply. According to the ECMAScript specification, when a primitive value is passed to apply, the value's toObject method is called:
If thisArg is null or undefined, the called function is passed the global object as the this value. Otherwise, the called function is passed ToObject(thisArg) as the this value.
This explains why your code was not working-- .find expects a string primitive and not a String object.
This is why the documentation for $.each actually mentions using this with primitive values:
(The value can also be accessed through the this keyword, but Javascript will always wrap the this value as an Object even if it is a simple string or number value.).
Therefore the way to fix the problem with your code is to leverage the element argument that's passed to the callback function:
$.each([".b"], function (_, item) {
console.log($('.a').find(item).text()); // Expecting to print "Test"
});
Don't use this inside each when looping over an array. It works fine on objects or collection of elemnts but fails with arrays.
Use second argument of each to access array element
$(['.b']).each(function (index, item) {
console.log($('.a').find(item).text()); // Expecting to print "Test"
});
DEMO http://jsfiddle.net/KbuZK/
That's because you're on the dark side of JavaScript.
In JavaScript, this is always made into an Object so that typeof this === "object", no matter what this was actually bound to. Even when a primitive string is bound to the this context (which you'd expect to give typeof this === "string"), this is actually a String object and typeof this === "object". Here's a more thorough explanation of this behaviour.
When iterating over arrays of non-objects, you should use the second argument of your callback function as value.
$(['.b']).each(function (index, value) {
console.log($('.a').find(value).text()); // Expecting to print "Test"
});
I have an object that contains circular references, and I would like to look at the JSON representation of it. For example, if I build this object:
var myObject = {member:{}};
myObject.member.child = {};
myObject.member.child.parent = myObject.member;
and try to call
JSON.stringify(myObject);
I get the error "too much recursion", not surprisingly. The "child" object has a reference to its "parent" and the parent has a reference to its child. The JSON representation does not have to be perfectly accurate, as I'm only using it for debugging, not for sending data to a server or serializing the object into a file or anything like that. Is there a way to tell JSON.stringify to just ignore certain properties (in this case the parent property of the child object), so that I would get:
{
"member" : {
"child" : {}
}
}
The closest I can think of is to use getChild() and getParent() methods instead of just members, because JSON.stringify ignores any properties that are functions, but I'd rather not do that if I don't have to.
You can pass a function as the second argument to stringify.
This function receives as arguments the key and value of the member to stringify.
If this function returns undefined, the member will be ignored.
alert(JSON.stringify(myObject, function(k, v) {
return (k === 'member') ? undefined : v;
}));
...or use e.g. firebug or use the toSource()-method, if you only want to see whats inside the object.
alert(myObject.toSource());
From the crockford implementation (which follows the ECMA specification):
If the stringify method sees an object that contains a toJSON method, it calls that method, and stringifies the value returned. This allows an object to determine its own JSON representation.
Then something like this should work just fine:
var myObject =
{
member: { child: {} }
}
myObject.member.child.parent = myObject.member;
myObject.member.child.toJSON = function ()
{
return 'no more recursion for you.';
};
console.log(JSON.stringify(myObject));
http://jsfiddle.net/feUtk/