0

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");
gerrod
  • 6,119
  • 6
  • 33
  • 45
  • I don't know how Spine works, but `JSON.stringify` only looks at "own properties" that are enumerable, not inherited or non-enumerable properties. What happens if you do: `console.log(Object.keys(instance));` In the resulting Array, do you get the super property? – I Hate Lazy Oct 28 '12 at 23:26
  • Good question - but it comes through correctly: ["SuperClassProperty", "SubClassProperty", "cid"] – gerrod Oct 28 '12 at 23:33
  • Strange. Is that the native `Object.keys` method? What happens if you do `instance.hasOwnProperty("SuperClassProperty")`? Also `Object.getOwnPropertyDescriptor(instance, "SuperClassProperty")` – I Hate Lazy Oct 28 '12 at 23:58
  • Hey, thanks for the help! Object.keys is native; instance.hasOwnProperty returns true; and getOwnPropertyDescriptor returns the same thing for both "SuperClassProperty" and "SubClassProperty" (different values of course). – gerrod Oct 29 '12 at 03:57
  • Do you experience this problem in Firefox as well, or just Chrome? – I Hate Lazy Oct 29 '12 at 04:10
  • Same problem regardless of browser. I've done a bit of debugging in Spine and will update the question with what I've found. – gerrod Oct 29 '12 at 05:43
  • Yeah sorry, I just really don't have any clue. If you're showing the keys on the object, I don't know why `.stringify()` would treat them any differently, unless they're not enumerable. Chrome has a bug where a property on a prototype can change the enumerability of a property on the instance if the properties have the same name, but I went back and checked, and it's actually the other way around, where the *enumerable* prototyped property lets the *non-enumerable* instance property be included in enumeration. So at this point, I really just don't know the answer. Sorry. – I Hate Lazy Oct 29 '12 at 13:55

1 Answers1

0

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.

gerrod
  • 6,119
  • 6
  • 33
  • 45