How to use JSON to re-build the Javascript Object? - javascript

I have an object like this:
var someObj = Class.create ({
initialize: function(objName){
this.objName = objName;
}
});
I can use
o = new someObj("objName");
to make an obj. I can use Object.toJSON(o) to change the o to become a JSON String,
but I want the JSON String convert back to someObj, so, I use eval() to pass the
JSON String to become an object, but the question is, it can become a JS Obj,
but the constructor of "o" is not someObj. How can I eval the JSON String by using
"someObj" as the constructor?

JSON strings cannot represent objects with member functions, so the only thing you will get out of a JSON string is raw data. Assuming the toJSON method results in a JSON string representing an object with all the non-function members of your class instance, you should be able to take the resulting object and attach the prototype to get all the functions back. For example, using jQuery's handy extend function:
var o = new someObj("objName");
var json = Object.toJSON(o);
var json_obj = eval(json);
$.extend(json_obj, someObj.prototype);
json_obj.someMethodDefinedOnsomeObj()
Depending on how the framework you are using to represent classes in JavaScript makes use of the prototypal object model, your milage may very with the above example. Also, using eval() creates a security hole, so if you do not trust where the JSON string is coming from, you should use a different de-serialization method. Just for full coverage, here is how I did it with raw prototypes:
function Animal(name){
this.name = name;
}
Animal.prototype.talk = function(){
console.log("My name is "+this.name);
}
var a = new Animal("Brendan Eich");
a.talk();
var json = '{name: "Tim Berners-Lee"}'
var b = eval(b);
$.extend(b, Animal.prototype);
b.talk();
In a firebug console this produces the output:
My name is Brendan Eich
My name is Tim Berners-Lee

See JSON revivers at http://json.org/js.html
var myObject = JSON.parse(myJSONtext, reviver);
The optional reviver parameter is a
function that will be called for every
key and value at every level of the
final result. Each value will be
replaced by the result of the reviver
function. This can be used to reform
generic objects into instances of
pseudoclasses, or to transform date
strings into Date objects.

you're using prototypejs right? i've always found that tricky and ended up just making my own initializer that read in an object that had been evaled or the json string itself.

Related

Is JSON in JavaScript stored in HashMap?

I searched JSON and HashMap, there are many questions about "how to convert json to hashmap". So, they are different, and how can i use hashmap in js?
Short answer would be “no”, because JSON is simply a interchange format written and parsable as a JavaScript object. If you want something like a hash map, you probably would just use an Object not-so-primitive, defining and deleting keys or values respectively:
var mapObj = {};
mapObj[key] = 'value';
delete mapObj[key];
There is also the Map object that might fit such an use, new in ES6:
var mapObj = new Map();
mapObj.set('key','value');
mapObj.get('key');
mapObj.delete('key');
You can serialize JavaScript objects by calling stringify on them and then parse them again:
var stringJSON = JSON.stringify(mapObj); // check your object structure for serializability!
var objsJSON = JSON.parse(stringJSON);
Serializing a Map is a bit different. If serialisable, you can do it anyway by using Array.from() and entries():
var strMap = JSON.stringify(Array.from(mapObj.entries()));
var mapObj = new Map(JSON.parse(strMap));
Wikipedia article states clearly that JSON is just text:
[JSON] uses human-readable text to transmit data objects
JSON is stored inside strings in Javascript. Javascript objects are essentially hashmaps. You can use JSON.parse() to convert a JSON string to an object.

How to hydrate a WinJS.Class

I am serializing and storing an object that was created from a WinJS.Class like this:
var myClass = WinJS.Class.define(...);
var myObject = new myClass();
var serialized = JSON.stringify(myObject);
//store the object
And later I'm pulling the object out of storage and I want to deserialize it and cast it as a myClass. Is that possible with WinJS out of the box or do I need to create a constructor for my class that is capable of taking an object that can turn it into a new object?
I haven't broken into TypeScript yet, and I think that would help out in this situation, but until then I'm wondering how to do it with plain JavaScript/WinJS.
There are a few ways to handle this, and none are particularly special to WinJS. Simply put: JSON serialization only serializes and deserializes the obje values, not its methods, prototype, or other type information.
Option 1: Copy values to new instance of your class
This is usually best accomplished by having your constructor take the deserialized object as a parameter and copying the data to the new instance.
There are a variety of variations of this. Using the object constructor is generally the best for performance, as this typically enables the JS engine to apply the greater number of optimizations to the object.
WinJS.UI.setOptions can be helpful here, or you can just copy the data using a simple loop like this:
var keys = Object.keys(source);
for (var i = 0, len = keys.length; i < len; i++) {
var key = keys[i];
destination[key] = source[key];
}
Option 2: Setting __proto__
Warning: This can have significantly adverse performance effects, so it's not appropriate in some situations. But occasionally it can be handy.
Object.setPrototypeOf(myObject, myClass.prototype);
Note that setPrototypeOf is relatively new. It's there on Win8.1 for web apps (which I'm guessing this is about) and in IE 11, but not available in Safari, for example. On older browsers/ Safari, assigning to proto is the equivalent (but if available, setPrototypeOf is better).
This will attach methods from myClass to the object, but in addition to the negative performance effects, also does not run your constructor on the object - so it still may not be in exactly the same state as the object you originally serialized.
Other helpful thing: JSON "revivers"
JSON.parse takes an optional second parameter, called a "reviver". This lets you provide a function that gets the opportunity to transform each node of the JSON being deserialized. This can be useful for rehydrating serialized dates into JavaScript Date objects, for example. It also gets the opportunity to transform the top-most object, which could be useful in some cases to turn the deserialized object into the "class" you want.
Javascript is a dynamic language so I think you dont need to cast the deserialized object, just treat it as myClass type and that's it. Hope it helps you.
You should consider using the 'Options' constructor pattern, where the option value is the deserialized object:
// MovieModel Constructor
// ----------------------
function MovieModel(options) {
this._titleValue = options.title || "Sample Title";
}
Where the movie methods closure is something like this:
// MovieModel Methods
// ------------------
var movieModelMethods = {
title: {
get: function () {
return this._titleValue;
},
set: function (val) {
this._titleValue = val;
this.dispatchEvent("title");
}
}
};
Since WinJS class define can only specify one constructor function (as far as I understand it), you may use the static members to define a factory function that will take the serialized data as a parameter. This factory methdod will actually create a new instance and will set the values one by one and return the new object.
It as some advantages like the fact that you can actually manage the data structure changes over the time you enhance the app...
The drawback is that you cannot write new MySuperClass() all the time...
...
// let's suppose we already called JSON.parse(data);
create: function(serializedData) {
var newObj = new MySuperClass();
newObj.name = serializedData.name || "";
newObj.color = serializedData.color || "";
return newObj;
}
Then you will call somewhere else in the app :
var myInstance = MySuperClass.create(serializedDataFromfile);
You should just be able to call JSON.parse after pulling it out of local storage:
var myObject2;
myObject2 = JSON.parse(localStorage["mySeriazliedObject"];

Why is JSON.stringify not serializing prototype values?

I have been working with a fair bit of JSON parsing and passing in Javascript within Node.js and browsers recently and bumped into this conundrum.
Any objects I created using a constructor, cannot be fully serialized fully via JSON.stringify, UNLESS I initialised all values within the constructor individually! This means my prototype becomes essentially useless in designing these classes.
Can someone shed some light on why the following doesn't serialize as I expect?
var ClassA = function () { this.initialisedValue = "You can see me!" };
ClassA.prototype = { initialisedValue : "You can't see me!", uninitialisedValue : "You can't see me!" };
var a = new ClassA();
var a_string = JSON.stringify(a);
What happens:
a_string == { "initialisedValue" : "You can see me!" }
I would expect:
a_string == { "initialisedValue" : "You can see me!", "uninitialisedValue" : "You can't see me!" }
Update (01-10-2019):
Finally noticed #ncardeli 's Answer, which does allow us to do something like the following to achieve my above requirement (in 2019!):
Replace
var a_string = JSON.stringify(a);
with
var a_string = JSON.stringify(a, Object.keys(ClassA.prototype));
Full code:
var ClassA = function () { this.initialisedValue = "You can see me!" };
ClassA.prototype = { initialisedValue : "You can't see me!", uninitialisedValue : "You can't see me!" };
var a = new ClassA();
var a_string = JSON.stringify(a, Object.keys(ClassA.prototype));
console.log(a_string)
Simply because this is the way JSON works. From the ES5 spec:
Let K be an internal List of Strings consisting of the names of all the own properties of value whose [[Enumerable]] attribute is true.
This makes sense, because there is no mechanism in the JSON specification for preserving information that would be required to parse a JSON string back into a JavaScript object if inherited properties were included. In your example, how would this parsed:
{ "initialisedValue" : "You can see me!", "uninitialisedValue" : "You can't see me!" }
There is no information to parse it into anything other than a flat object with 2 key-value pairs.
And if you think about it, JSON is not intended to map directly to JavaScript objects. Other languages must be able to parse JSON strings into simple structures of name-value pairs. If JSON strings contained all the information necessary to serialize complete JavaScript scope chains, other languages may be less capable of parsing that into something useful. In the words of Douglas Crockford on json.org:
These [hash tables and arrays] are universal data structures. Virtually all modern programming languages support them in one form or another. It makes sense that a data format that is interchangeable with programming languages also be based on these structures.
I'd like to add that, even though JSON.stringify will only stringify the object's own properties, as explained in the accepted answer, you can alter the behavior of the stringification process by specifying an array of String as the second parameter of JSON.stringify (called a replacer array).
If you specify an array of String with the whitelist of properties to stringify, the stringification algorithm will change its behavior and it will consider properties in the prototype chain.
From ES5 spec:
If PropertyList is not undefined, then
a. Let K be PropertyList.
Else
a. Let K be an internal List of Strings consisting of the names of
all the own properties of value whose [[Enumerable]] attribute is
true. The ordering of the Strings should be the same as that used by
the Object.keys standard built-in function.
If you know the name of the properties of the object to stringify beforehand, you can do something like this:
var a_string = JSON.stringify(a, ["initialisedValue", "uninitialisedValue"]);
// a_string == { "initialisedValue" : "You can see me!", "uninitialisedValue" : "You can't see me!" }
There is another possibility to still have properties from the prototype to be stringified.
As of the JSON spec (15.12.3 stringify http://es5.github.io/#x15.12.3):
[...]
2. If Type(value) is Object, then
a. Let toJSON be the result of calling the [[Get]] internal method of value with argument "toJSON".
b. If IsCallable(toJSON) is true
i. Let value be the result of calling the [[Call]] internal method of toJSON passing value as the this value and with an argument list consisting of key.
[...]
So yu can write your own JSON stringifier function. A general implementation could be:
class Cat {
constructor(age) {
this.age = age;
}
get callSound() {
// This does not work as this getter-property is not enumerable
return "Meow";
}
toJSON() {
const jsonObj = {}
const self = this; // If you can use arrow functions just use 'this'-keyword.
// Object.keys will list all 'enumerable' properties
// First we look at all own properties of 'this'
Object.keys(this).forEach(function(k) {
jsonObj[k] = self[k];
});
// Then we look at all own properties of this's 'prototype'
Object.keys(Object.getPrototypeOf(this)).forEach(function(k) {
jsonObj[k] = self[k];
});
return JSON.stringify(jsonObj);
}
}
Object.defineProperty(Cat.prototype, 'callSound2', {
// The 'enumerable: true' is important!
value: "MeowMeow", enumerable: true
});
let aCat = new Cat(4);
console.log(JSON.stringify(aCat));
// prints "{\"age\":4,\"callSound2\":\"MeowMeow\"}"
This works as long as the right properties (those you actually want to be JSON stringified) are enumerable and those you dont want to be stringified aren't. So you need to be careful which properties you make enumerable or become implicitly enumerable when you assign values to 'this'.
Another possibility is to assign each property you actually want to be stringifed manually one by one. Which might be less error prone.

what is the difference here in this use of JSON.stringify()?

what is the difference in the following between the result of p and q and why would you do either way, which is best?
var my = [
{"a":"sdsds"},
{"b":"sdsds"},
{"c":"sdsds"},
{"d":"sdsds"},
{"e":"sdsds"}
];
var p = JSON.stringify({ "myText": my };);
var q = { "myText": JSON.stringify(my) };
p is a string containing:
'{"myText":[{"a":"sdsds"},{"b":"sdsds"},{"c":"sdsds"},{"d":"sdsds"},{"e":"sdsds"}]}'
q is an object:
{
myText: '[{"a":"sdsds"},{"b":"sdsds"},{"c":"sdsds"},{"d":"sdsds"},{"e":"sdsds"}]'
}
They're not the same thing, so I can't tell you which is best. What do you want to use it for?
p is a string that looks like "{ \"mytext\": ... }".
q is an object with a property called mytext.
One creates a JSON text consisting of an object with the property 'myText' with the value being the data that 'my' contains (i.e. an array of objects each of which has one property/string pair).
The other creates an object consisting of a property 'myText' with the value being a string containing a JSON text built from the data in 'my'.
why would you do either way
The former is generally the approach taken when creating JSON.
That latter might be useful if you planned to pass the object to something like jQuery's data property in an .ajax() call.
which is best
Neither. They simply different. "Best" is whatever works for what you are going to do with the variables.

Does String inherit from Object in Javascript?

Is Object the base class of all objects in Javascript, just like other language such as Java & C#?
I tried below code in Firefox with Firebug installed.
var t = new Object();
var s1 = new String('str');
var s2 = 'str';
console.log(typeof t);
console.log(typeof s1);
console.log(typeof s2);
The console output is
object
object
string
So, s1 and s2 are of diffeent type?
Yes, 'str' is a string literal, not a string object.
A string literal has access to all of a string's objects and methods because javascript will temporarily cast a string literal as a string object in order to run the desired method.
Finally:
Where the two differ is their treatment of new properties and methods. Like all Javascript Objects you can assign properties and methods to any String object. You can not add properties or methods to a string literal. They are ignored by the interpreter.
Read up more here.
The process is called boxing/unboxing.
This means that whenever the interpreter/compiler sees a primitive type used as an Object then it will use
new Object([primitive])
to get a valid instance. And in the same way, as soon as you try to use it as a primitive (as in an expression) it will use
[boxedobject].valueOf()
to get the primitive.
In ECMAScript (javascript) the constructor of Object is able to box all primitives.
Read this: http://skypoetsworld.blogspot.com/2007/11/javascript-string-primitive-or-object.html
and this: https://developer.mozilla.org/en/JavaScript/Glossary#primitive
and this: https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/String

Categories