I'm seeing strange behaviour using JSON.stringify against a subclassed model in Spine, and I'm hoping someone can help!
Here's a simplified excerpt from some code that we've got on one of our projects:
define([
"jquery",
"underscore"
],
function ($, _) {
var SuperClass = Spine.Model.sub();
SuperClass.configure("SuperClass", "SuperClassProperty");
var SubClass = SuperClass.sub();
SubClass.configure("SubClass", "SubClassProperty");
var instance = new SubClass({ SuperClassProperty: "Super", SubClassProperty: "Sub" });
console.log(instance);
var json = JSON.stringify(instance);
console.log(json);
});
The "console.log(instance)" is printing out exactly what I would expect in this scenario:
result
SubClassProperty: "Sub"
SuperClassProperty: "Super"
cid: "c-0"
__proto__: ctor
However, when I use JSON.stringify against the instance, this is all that I am returned:
{"SubClassProperty":"Sub"}
Why doesn't the SuperClassProperty get included in the stringify?
I've ruled out a problem with the JSON.stringify method by forcing JSON2 to override Chrome's native JSON object; both implementations yield the same result. It looks like stringify will delegate to the "toJSON" function on the object if there is one - and in this case there is (as part of Spine).
So it looks like either (a) this is a bug in Spine, or (b) I'm doing something incorrectly, which is the more likely option.
I know I can work around this problem by re-configuring the superclass properties on the subclass as well:
SubClass.configure("SubClass", "SuperClassProperty", "SubClassProperty");
However this seems counter-intuitive to me (what's the point of subclassing?), so I'm hoping that's not the answer.
Update: I've done some debugging through the Spine source code, and from what I can tell the problem is the way that I'm configuring the subclass:
var SubClass = SuperClass.sub();
SubClass.configure("SubClass", "SubClassProperty");
Calling "configure" here appears to wipe out the attributes from SuperClass. The "toJSON" implementation on the Model prototype is as follows:
Model.prototype.toJSON = function() {
return this.attributes();
};
Since the attributes collection is reset when SubClass is configured, the SuperClass properties don't come through in the JSON string.
I'm not sure if I shouldn't be calling "configure" on subclassed objects, but I can't find anywhere in the documentation that says I should be doing something else - this is the only reference I can find for subclassing Models (from: http://spinejs.com/docs/models):
Models can be also be easily subclassed:
var User = Contact.sub(); User.configure("User");
As I suspected, the problem was in the way that I'm using Spine. This comment from the author of Spine infers that using "configure" on a subclass will wipe out the attributes of the superclass. I have to admit I don't understand why this is; it seems counter-intuitive to me, but at least I now know that it's not a bug.
In case anyone else runs into this issue, the way I've worked around it is by adding my own extension method to the Spine Model as follows:
(function () {
var Model = Spine.Model;
Model.configureSub = function () {
var baseAttributes = this.attributes.slice();
this.configure.apply(this, arguments);
this.attributes = baseAttributes.concat(this.attributes);
return this;
};
})();
Now to configure my subclass:
var SubClass = SuperClass.sub();
SubClass.configureSub("SubClass", "SubClassProperty");
And now my JSON correctly reflects the properties from both the super and subclasses.