8

I'm trying to store an object in redis, which is an instance of a class, and thus has functions, here's an example:

function myClass(){
    this._attr = "foo";
    this.getAttr = function(){
        return this._attr;
    }
}

Is there a way to store this object in redis, along with the functions? I tried JSON.stringify() but only the properties are preserved. How can I store the function definitions and be able to perform something like the following:

var myObj = new myClass();
var stringObj = JSON.stringify(myObj);
// store in redis and retreive as stringObj again
var parsedObj = JSON.parse(stringObj);

console.log(myObj.getAttr()); //prints foo
console.log(parsedObj.getAttr()); // prints "Object has no method 'getAttr'"

How can I get foo when calling parsedObj.getAttr()?

Thank you in advance!

EDIT

Got a suggestion to modify the MyClass.prototype and store the values, but what about something like this (functions other than setter/getter):

function myClass(){
    this._attr = "foo";
    this._accessCounts = 0;
    this.getAttr = function(){
        this._accessCounts++;
        return this._attr;
    }
    this.getCount = function(){
        return this._accessCounts;
    }
}

I'm trying to illustrate a function that calculates something like a count or an average whenever it is called, apart from doing other stuff.

scanales
  • 602
  • 1
  • 7
  • 18

5 Answers5

13

First, you are not defining a class.

It's just an object, with a property whose value is a function (All its member functions defined in constructor will be copied when create a new instance, that's why I say it's not a class.)

Which will be stripped off when using JSON.stringify.

Consider you are using node.js which is using V8, the best way is to define a real class, and play a little magic with __proto__. Which will work fine no matter how many property you used in your class (as long as every property is using primitive data types.)

Here is an example:

function MyClass(){
  this._attr = "foo";
}
MyClass.prototype = {
  getAttr: function(){
    return this._attr;
  }
};
var myClass = new MyClass();
var json = JSON.stringify(myClass);

var newMyClass = JSON.parse(json);
newMyClass.__proto__ = MyClass.prototype;

console.log(newMyClass instanceof MyClass, newMyClass.getAttr());

which will output:

true "foo"
xiaoyi
  • 6,641
  • 1
  • 34
  • 51
  • 1
    @scanales if it's a plain object, it will work fine. but if it's a instance of a class, you will need to set its `__proto__` recursively. – xiaoyi Nov 27 '12 at 18:46
6

No, JSON does not store functions (which would be quite inefficient, too). Instead, use a serialisation method and a deserialisation constructor. Example:

function MyClass(){
    this._attr = "foo";
    this.getAttr = function(){
        return this._attr;
    }
}
MyClass.prototype.toJSON() {
    return {attr: this.getAttr()}; // everything that needs to get stored
};
MyClass.fromJSON = function(obj) {
    if (typeof obj == "string") obj = JSON.parse(obj);
    var instance = new MyClass;
    instance._attr = obj.attr;
    return instance;
};
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • But what about functions that are not setter/getter, I added them to my question – scanales Nov 27 '12 at 18:15
  • @scanales: `getCount` is a getter method, too, isn't it? – Bergi Nov 27 '12 at 18:27
  • I meant the increment to accessCounts on the getAttr, which doesn't only get a static property as before. – scanales Nov 27 '12 at 18:41
  • As `accessCounts` is also just a [public] property of the object, there is no difference to `attr`. – Bergi Nov 27 '12 at 20:22
  • will the `JSON.stringify` make use of the `toJSON()` methods, or do you mean that we should just call the `toJSON()` directly without using `JSON.stringify`? – bvdb Nov 06 '19 at 08:55
  • 1
    @bvdb `JSON.stringify` will call `toJSON()` on all objects that it visits – Bergi Nov 06 '19 at 13:01
0

Scanales, I had the same issue and tried a technique similar to Bergi's recommendation of creating new serialization/deserialization methods...but found it didn't work for me because I have objects nested in objects (several deep). If that's your case then here's how I solved it. I wrote a base class (clsPersistableObject) from which all objects that I wanted to persist inherited from. The base class has a method called deserialize, which is passed the JSON string. This method sets the properties one by one (but does not wipe out the exist methods) and then recursively defer to the child object to do the same (as many times as necessary).

deserialize: function (vstrString) {
                //.parse: convert JSON string to object state


                //Use JSON to quickly parse into temp object (does a deep restore of all properties)
                var tmpObject = JSON.parse(vstrString);

                //objZoo2.animal.move();

                //Note: can't just do something like this: 
                //  CopyProperties(tmpObject, this);
                //because it will blindly replace the deep objects 
                //completely...inadvertently wiping out methods on it.  Instead:
                //1) set the properties manually/one-by-one.
                //2) on objects, defer to the deserialize on the child object (if it inherits clsPersistableObject)
                //2b) if it doesn't inherit it, it's an intrinsic type, etc...just do a JSON parse.

                //loop through all properties
                var objProperty;
                for (objProperty in tmpObject) {

                    //get property name and value
                    var strPropertyName = objProperty;
                    var strPropertyValue = tmpObject[objProperty]; //note: doing this .toString() will cause

                    if (objProperty !== undefined) {
                        //check type of property
                        if (typeof strPropertyValue == "object") {
                            //object property: call it recursively (and return that value)

                            var strPropertyValue_AsString = JSON.stringify(strPropertyValue);

                            //see if has a deserialize (i.e. inherited from clsPeristableObject)
                            if ("deserialize" in this[objProperty]) {
                                //yes: call it
                                this[objProperty]["deserialize"](strPropertyValue_AsString);
                            }
                            else {
                                //no: call normal JSON to deserialize this object and all below it
                                this[objProperty] = JSON.parse(strPropertyValue_AsString);
                            }  //end else on if ("deserialize" in this[objProperty]) 
                        }
                        else {
                            //normal property: set it on "this"
                            this[objProperty] = tmpObject[objProperty];
                        } //end else on if (typeof strPropertyValue == "object") 
                    } //end if (objProperty !== undefined) 
                }

            }
Ian Ippolito
  • 486
  • 4
  • 7
-1

it looks like you attempt to stringify a closed function. you can use ()=>{} to solve the scope problem.

function myClass(){
    this._attr = "foo";
    this._accessCounts = 0;
    this.getAttr = ()=>{
        this._accessCounts++;
        return this._attr;
    }
    this.getCount = ()=>{
        return this._accessCounts;
    }
}
Cery
  • 203
  • 5
  • 14
-3

What you get back grom JSON.stringify() is a String. A string has no methods. You need to eval first that string and then you'll be able to get the original object and its methods.

var myObj = new myClass();
var stringObj = JSON.stringify(myObj);

---- EDIT -----

//Sorry use this:
var getBackObj  = JSON.parse(stringObj);
//Not this
var getBackObj = eval(stringObj);

console.log(getBackObj.getAttr()); // this should work now
kante
  • 229
  • 1
  • 4
  • 13
  • 2
    `JSON.stringify` just skips function members so this won't work. – JohnnyHK Nov 27 '12 at 17:58
  • I found this function `function toJSONWithFuncs(obj) { Object.prototype.toJSON = function() { var sobj = {}, i; for (i in this) if (this.hasOwnProperty(i)) sobj[i] = typeof this[i] == 'function' ? this[i].toString() : this[i]; return sobj; }; var str = JSON.stringify(obj); delete Object.prototype.toJSON; return str; }` on [http://stackoverflow.com/questions/3685703/javascript-stringify-object-including-members-of-type-function](this post), and it stringifies the functions, but cant get them bk – scanales Nov 27 '12 at 18:05
  • can't get them back in a usable way, any thoughts? – scanales Nov 27 '12 at 18:05
  • 1
    eval = evil. Especially dont trust information youre getting back from a server with eval. That being said, JSON.parse / stringify doesn't include nonenumerable properties ( like prototype ). havent tried @scanales function but it seems legit, except for the delete Object.prototype.toJSON part. deletes are inherently memory intensive, thats why google's js styleguide says not to do it, but instead set the property to null – Andrew Luhring May 08 '15 at 16:39