I lately was experimenting with the object serialization in JavaScript. I have already been looking through some of the questions concerning the serialization and deserialization of predefined object in Javascript, but I am looking for a more general solution. An example of this would be:
function anObject(){
var x = 1;
this.test = function(){return x;};
this.add = function(a){x+a;};
}
var x = new anObject();
x.add(2);
console.log(x.test());
>>> 3
var y = deserialize(serialize(x));
console.log(y.test());
>>> 3
Is there a way to serialize this object and deserialize it, such that the deserialized object still have access to the local variable x without the use of the prototype of that object (like in this solution)?
I have already tried by just storing the function as a string and evaluating it again, but then the state of an object can not be saved.
What you are trying to do is not possible without code introspection and code re-writing which I think is not a good idea. However, what about something like this?
function AnObject() {
var x = 1;
this.x = function () { return x; };
this.addToX = function (num) { x += num; };
this.memento = function () {
return { x: x };
};
this.restoreState = function (memento) {
x = memento.x;
};
}
var o = new AnObject();
o.addToX(2);
o.x(); //3
var serializedState = JSON.stringify(o.memento()),
o = new AnObject();
o.restoreState(JSON.parse(serializedState));
o.x(); //3
However, please note that having priviledged members comes at a great cost because you lose the benefits of using prototypes. For that reason I prefer not enforcing true privacy and rely on naming conventions such as this._myPrivateVariable instead (unless you are hiding members of a module).
Thanks for the responses. While the answer from plalx works perfectly for specific objects, I wanted to have something more general which just works for any object you throw at it.
Another solution one can use is something like this:
function construct(constructor, args, vars) {
function Obj() {
var variables = vars
return constructor.apply(this, args);
}
Obj.prototype = constructor.prototype;
return new Obj();
}
function addFunction(anObject, aFunction, variables) {
var objectSource = anObject.toString();
var functionSource = aFunction.toString();
objectSource = objectSource.substring(0,objectSource.length-1);
var functionName = functionSource.substring(9, functionSource.indexOf('('));
var functionArgs = functionSource.substring(functionSource.indexOf('('), functionSource.indexOf('{')+1);
var functionBody = functionSource.substring(functionSource.indexOf('{')+1, functionSource.length);
return objectSource + "this." + functionName + " = function" +
functionArgs + "var variables = " + variables + ";\n" + functionBody + "}";
}
function makeSerializable(anObject) {
var obj = JSON.stringify(anObject, function(key, val) {
return ((typeof val === "function") ? val+'' : val);
});
var variables = [];
while(obj.indexOf("var") > -1) {
var subString = obj.substring(obj.indexOf("var")+3, obj.length-1);
while (subString[0] == " ")
subString = subString.replace(" ", "");
var varEnd = Math.min(subString.indexOf(" "), subString.indexOf(";"));
var varName = subString.substring(0, varEnd);
variables.push(varName);
obj = obj.replace("var","");
}
var anObjectSource = addFunction(anObject,
function serialize(){
var vars = [];
console.log("hidden variables:" + variables);
variables.forEach(function(variable) {
console.log(variable + ": " + eval(variable));
vars += JSON.stringify([variable, eval(variable)]);
});
var serialized = [];
serialized.push(vars);
for (var func in this){
if (func != "serialize")
serialized.push([func, this[func].toString()]);
}
return JSON.stringify(serialized);
},
JSON.stringify(variables));
anObject = Function("return " + anObjectSource)();
var params = Array.prototype.slice.call(arguments);
params.shift();
return construct(anObject, params, variables);
}
This allows you to serialize all elements of any object, including the hidden variables. The serialize() function can then be replaced by a custom string representation for the hidden variables, which can be used when deserializing the string representation to the object.
usage:
function anObject(){
var x = 1;
var y = [1,2];
var z = {"name": "test"};
this.test = function(){return x;};
this.add = function(a){x+a;};
}
var test = makeSerializable(anObject)
test.serialize()
>>>["[\"x\",1][\"y\",[1,2]][\"z\",{\"name\":\"test\"}]",["test","function (){return x;}"],["add","function (a){x+a;}"]]
Related
I need to implement inheritance tree in JavaScript where each node can have more than 1 parent. We have to implement Object.Create and Object.call methods on our own. We are specifically not allowed to use new keyword. Here is what I have so far:
var myObject = {
hash:0,
parents: [],
create: function(args){
//TODO check if not circular
if(args instanceof Array){
for(i=0;i<args.length;i++){
this.parents.push(args[i]);
}
}
return this;
},
call : function(fun,args){
//TODO: dfs through parents
return this[fun].apply(this,args);
},
}
var obj0 = myObject.create(null);
obj0.func = function(arg) { return "func0: " + arg; };
var obj1 = myObject.create([obj0]);
var obj2 = myObject.create([]);
obj2.func = function(arg) { return "func2: " + arg; };
var obj3 = myObject.create([obj1, obj2]);
var result = obj0.call("func", ["hello"]);
alert(result);
//calls the function of obj2 istead of obj0
The problem with this code is that I get a call to obj2's function instead of obj0's. I'm suspecting that create() function should not return this, but something else instead (create instance of itself somehow).
In your current solution, you are not actually creating a new object with your myObject.create() function, you are just using the same existing object and resetting it's parent array. Then, when you define .func() you are overriding that value, which is why func2: appears in your alert.
What you need to do is actually return a brand new object. returning this in your myObject.create() will just return your existing object, which is why things are getting overridden.
To avoid using the new keyword, you'll want to do either functional inheritance or prototypal inheritance. The following solution is functional inheritance:
function myObject (possibleParents) {
//create a new node
var node = {};
//set it's parents
node.parents = [];
//populate it's parents if passed in
if (possibleParents) {
if (possibleParents instanceof Array) {
for (var index = 0; index < possibleParents.length; index++) {
node.parents.push(possibleParents[index]);
}
} else {
node.parents.push(possibleParents);
};
}
//
node.call = function(fun,args) {
return this[fun].apply(this,args);
};
return node;
};
var obj0 = myObject();
obj0.func = function(arg) { return "func0: " + arg; };
var obj1 = myObject([obj0]);
var obj2 = myObject();
obj2.func = function(arg) { return "func2: " + arg; };
var obj3 = myObject([obj1, obj2]);
var result = obj0.call("func", ["hello"]);
alert(result); // this will successfully call "func0: " + arg since you created a new object
I managed fix this problem only by using function instead of variable.
function myObject () {
this.parents = [];
this.setParents = function(parents){
if(parents instanceof Array){
for(i=0;i<parents.length;i++){
this.parents.push(parents[i]);
}
}
};
this.call = function(fun,args) {
return this[fun].apply(this,args);
};
}
var obj0 = new myObject();
obj0.func = function(arg) { return "func0: " + arg; };
var obj2 = new myObject();
obj2.func = function(arg) { return "func2: " + arg; };
var result = obj0.call("func", ["hello"]);
alert(result);
There are a couple of similar questions but none covers the case when a string looks like some-name[][some-key]. I have tried JSON.parse('some-name[][some-key]'); but it doesn't parse it.
Is there a way to convert such string to a JavaScript object that will look like { 'some-name': { 0: { 'some-key': '' } } }?
This is a name of a form field. It's normally parsed by PHP but I'd like to parse it with JavaScript the same way. I basically have <input name="some-name[][some-key]"> and I'd like to convert that to var something = { 'some-name': { 0: { 'some-key': VALUE-OF-THIS-FIELD } } }.
Try this:
JSON.parse('{ "some-name": [ { "some-key": "" } ] }');
I don't know exactly how you're doing this, but assuming they are all that format (name[][key]) and you need to do them one by one - this works for me:
var fieldObj = {};
function parseFieldName(nameStr)
{
var parts = nameStr.match(/[^[\]]+/g);
var name = parts[0];
var key = typeof parts[parts.length-1] != 'undefined' ? parts[parts.length-1] : false;
if(key===false) return false;
else
{
if(!fieldObj.hasOwnProperty(name)) fieldObj[name] = [];
var o = {};
o[key] = 'val';
fieldObj[name].push(o);
}
}
parseFieldName('some-name[][some-key]');
parseFieldName('some-name[][some-key2]');
parseFieldName('some-name2[][some-key]');
console.log(fieldObj); //Firebug shows: Object { some-name=[2], some-name2=[1]} -- stringified: {"some-name":[{"some-key":"val"},{"some-key2":"val"}],"some-name2":[{"some-key":"val"}]}
o[key] = 'val'; could of course be changed to o[key] = $("[name="+nameStr+"]").val() or however you want to deal with it.
Try this:
var input = …,
something = {};
var names = input.name.match(/^[^[\]]*|[^[\]]*(?=\])/g);
for (var o=something, i=0; i<names.length-1; i++) {
if (names[i])
o = o[names[i]] || (o[names[i]] = names[i+1] ? {} : []);
else
o.push(o = names[i+1] ? {} : []);
}
if (names[i])
o[names[i]] = input.value;
else
o.push(input.value);
Edit: according to your updated example, you can make something like this (view below). This will work - but only with the current example.
var convertor = function(element) {
var elementName = element.getAttribute('name');
var inpIndex = elementName.substring(0, elementName.indexOf('[')),
keyIndex = elementName.substring(elementName.lastIndexOf('[') + 1, elementName.lastIndexOf(']'));
var strToObj = "var x = {'" + inpIndex + "': [{'" + keyIndex + "': '" + element.value + "'}]}";
eval(strToObj);
return x;
};
var myObject = convertor(document.getElementById('yourInputID'));
Example here: http://paulrad.com/stackoverflow/string-to-array-object.html
(result is visible in the console.log)
old response
Use eval.. but your string must have a valid javascript syntax
So:
var str = "arr[][123] = 'toto'";
eval(str);
console.log(arr);
Will return a syntax error
Valid syntax will be:
var str = "var arr = []; arr[123] = 'toto'";
var x = eval(str);
console.log(arr);
i think i did not understand javascript module pattern.
I just create this module:
var mycompany = {};
mycompany.mymodule = (function() {
var my = {};
var count = 0;
my.init = function(value) {
_setCount(value);
}
// private functions
var _setCount = function(newValue) {
count = newValue;
}
var _getCount = function() {
return count;
}
my.incrementCount = function() {
_setCount(_getCount() + 1);
}
my.degreeseCount = function() {
_setCount(_getCount() - 1);
}
my.status = function() {
return count;
}
return my;
})();
var a = mycompany.mymodule;
var b = mycompany.mymodule;
console.debug(a, 'A at beginning');
console.debug(a, 'B at beginning');
a.init(5);
b.init(2);
console.log('A: ' + a.status()); // return 2 (wtf!)
console.log('B: ' + b.status()); // return 2`
Where is the mistake?
I thought that my code would have returned to me not 2 value, but 5.
What's the reason?
a and b are the exact same objects.
var a = mycompany.mymodule;
var b = mycompany.mymodule;
What you want to do is create two different objects which have the same prototype. Something similar to this:
mycompany.mymodule = (function () {
var my = function () {};
my.prototype.init = function (value) {
_setCount(value);
};
my.prototype.incrementCount = ...
// ...
return my;
}());
a = new mycompany.mymodule();
b = new mycompany.mymodule();
a.init(5);
b.init(2);
For more info, research "javascript prototypal inheritance"
In JavaScript, objects are passed by reference, not copied.
To explain further, here is a simplified version of your code:
var pkg = (function () {
var x = {};
return x;
}());
var a = pkg;
var b = pkg;
You do not create two separate objects but only reference the object pointed at by pkg from both a and b. a and b are exactly the same.
a === b // true
This means that calling a method on a you are ultimately doing the same to b (it points to the same object—x.)
You don't want to use the module pattern for this. You want the usual constructor+prototype.
function Pkg() {
this.count = 0;
};
Pkg.prototype.init = function (count) { this.count = count; };
var a = new Pkg();
var b = new Pkg();
a === b // false
a.init(2);
a.count === 2 // true
b.count === 2 // false
Here is a good read about module pattern.
I'm trying to make a script to use multiple values in window.location.hash but i'm having a problem with the $.extend() function of jquery
I've tried two ways, but both didn't work out.
var MultiHash = {
params: {},
getHash: function () {
var hashString = document.location.hash.replace('#', '').split('&');
for (var i=0; i < hashString.length; i++) {
var key = hashString[i].split('=')[0];
var value = decodeURIComponent(hashString[i].split('=')[1]);
// First way
var a = {key: value};
// Second way
var a = {};
a[key] = value;
$.extend(params, a);
}
return params;
},
...
}
Is anyone seeing the problem?
first you should write :
$.extend(this.params, a); or you cant access param
there may be other issues.
EDIT
it makes sense you return a instead of this.params in my opinion.
$.extend(a,this.params);
return a
There are two problems wrong with what you're trying to do. The first of which being a reference problem as the params variable for that object should be referenced as this.params. The second problem being that you are not saving the result of the object extension. All of this occurs in the following line:
$.extend(params, a);
It should read something like this instead:
this.params = $.extend(this.params, a);
try this one :
var MultiHash = {
params: {},
getHash: function () {
var hashString = document.location.hash.replace('#', '').split('&');
var a = [];
for (var i=0; i < hashString.length; i++) {
var key = hashString[i].split('=')[0];
var value = decodeURIComponent(hashString[i].split('=')[1]);
a.push(key + ":'" + value + "'");
}
$.extend(this.params,(new Function("return {" + a.Join(",") + "}"))());
return this.params;
},
...
}
I have this JavaScript prototype:
Utils.MyClass1 = function(id, member) {
this.id = id;
this.member = member;
}
and I create a new object:
var myobject = new MyClass1("5678999", "text");
If I do:
console.log(JSON.stringify(myobject));
the result is:
{"id":"5678999", "member":"text"}
but I need for the type of the objects to be included in the JSON string, like this:
"MyClass1": { "id":"5678999", "member":"text"}
Is there a fast way to do this using a framework or something? Or do I need to implement a toJson() method in the class and do it manually?
var myobject = new MyClass1("5678999", "text");
var dto = { MyClass1: myobject };
console.log(JSON.stringify(dto));
EDIT:
JSON.stringify will stringify all 'properties' of your class. If you want to persist only some of them, you can specify them individually like this:
var dto = { MyClass1: {
property1: myobject.property1,
property2: myobject.property2
}};
It's just JSON? You can stringify() JSON:
var obj = {
cons: [[String, 'some', 'somemore']],
func: function(param, param2){
param2.some = 'bla';
}
};
var text = JSON.stringify(obj);
And parse back to JSON again with parse():
var myVar = JSON.parse(text);
If you have functions in the object, use this to serialize:
function objToString(obj, ndeep) {
switch(typeof obj){
case "string": return '"'+obj+'"';
case "function": return obj.name || obj.toString();
case "object":
var indent = Array(ndeep||1).join('\t'), isArray = Array.isArray(obj);
return ('{['[+isArray] + Object.keys(obj).map(function(key){
return '\n\t' + indent +(isArray?'': key + ': ' )+ objToString(obj[key], (ndeep||1)+1);
}).join(',') + '\n' + indent + '}]'[+isArray]).replace(/[\s\t\n]+(?=(?:[^\'"]*[\'"][^\'"]*[\'"])*[^\'"]*$)/g,'');
default: return obj.toString();
}
}
Examples:
Serialize:
var text = objToString(obj); //To Serialize Object
Result:
"{cons:[[String,"some","somemore"]],func:function(param,param2){param2.some='bla';}}"
Deserialize:
Var myObj = eval('('+text+')');//To UnSerialize
Result:
Object {cons: Array[1], func: function, spoof: function}
Well, the type of an element is not standardly serialized, so you should add it manually. For example
var myobject = new MyClass1("5678999", "text");
var toJSONobject = { objectType: myobject.constructor, objectProperties: myobject };
console.log(JSON.stringify(toJSONobject));
Good luck!
edit: changed typeof to the correct .constructor. See https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/constructor for more information on the constructor property for Objects.
This might be useful.
http://nanodeath.github.com/HydrateJS/
https://github.com/nanodeath/HydrateJS
Use hydrate.stringify to serialize the object and hydrate.parse to deserialize.
You can use a named function on the constructor.
MyClass1 = function foo(id, member) {
this.id = id;
this.member = member;
}
var myobject = new MyClass1("5678999", "text");
console.log( myobject.constructor );
//function foo(id, member) {
// this.id = id;
// this.member = member;
//}
You could use a regex to parse out 'foo' from myobject.constructor and use that to get the name.
Below is another way by which we can JSON data with JSON.stringify() function
var Utils = {};
Utils.MyClass1 = function (id, member) {
this.id = id;
this.member = member;
}
var myobject = { MyClass1: new Utils.MyClass1("5678999", "text") };
alert(JSON.stringify(myobject));
function ArrayToObject( arr ) {
var obj = {};
for (var i = 0; i < arr.length; ++i){
var name = arr[i].name;
var value = arr[i].value;
obj[name] = arr[i].value;
}
return obj;
}
var form_data = $('#my_form').serializeArray();
form_data = ArrayToObject( form_data );
form_data.action = event.target.id;
form_data.target = event.target.dataset.event;
console.log( form_data );
$.post("/api/v1/control/", form_data, function( response ){
console.log(response);
}).done(function( response ) {
$('#message_box').html('SUCCESS');
})
.fail(function( ) { $('#message_box').html('FAIL'); })
.always(function( ) { /*$('#message_box').html('SUCCESS');*/ });
I was having some issues using the above solutions with an "associative array" type object. These solutions seem to preserve the values, but they do not preserve the actual names of the objects that those values are associated with, which can cause some issues. So I put together the following functions which I am using instead:
function flattenAssocArr(object) {
if(typeof object == "object") {
var keys = [];
keys[0] = "ASSOCARR";
keys.push(...Object.keys(object));
var outArr = [];
outArr[0] = keys;
for(var i = 1; i < keys.length; i++) {
outArr[i] = flattenAssocArr(object[keys[i]])
}
return outArr;
} else {
return object;
}
}
function expandAssocArr(object) {
if(typeof object !== "object")
return object;
var keys = object[0];
var newObj = new Object();
if(keys[0] === "ASSOCARR") {
for(var i = 1; i < keys.length; i++) {
newObj[keys[i]] = expandAssocArr(object[i])
}
}
return newObj;
}
Note that these can't be used with any arbitrary object -- basically it creates a new array, stores the keys as element 0, with the data following it. So if you try to load an array that isn't created with these functions having element 0 as a key list, the results might be...interesting :)
I'm using it like this:
var objAsString = JSON.stringify(flattenAssocArr(globalDataset));
var strAsObject = expandAssocArr(JSON.parse(objAsString));