Scott Sauyet
3/5/2016 3:18:00 AM
Simon Blackwell wrote:
> I suppose I was not clear in my request. A shortcoming with JSON is that
> natively it only supports de-serializing to Objects or Arrays. You have to
> provide a reviver function to get alternate behavior but JSON provides no
> native way of capturing the semantics required to revive objects into their
> original classes. And, if only standard serialized JSON is sent over to a
> server there is nothing to tell the server how to restore it with semantics.
Ok, this makes much more sense than what you originally posted. On USENET
it is traditional to give enough information in the post that a user does
not have to visit external resources to research the topic. URLs are fine,
but the content posted should stand by itself. This post gives enough
information that I was interested in checking out the library.
> What we implemented in cycler.js is a light weight schemaless way of passing
> semantics. If the JavaScript class definitions are the same on the client
> and server, with cycler you are essentially done (the class defintions
> essentially act as schema).
I'm afraid I'm not particularly impressed. There is mention in the
documentation of Douglas Crockford, and if I'm not mistaken, he is
currently suggesting a form of OOP in Javascript that would not work
with your tool.
I don't do a great deal of OOP Javascript myself, but when I do, I
also prefer a similar style, one which chooses strong encapsulation
over shared prototype methods. It's very useful for dealing with
immutable structures. Here is is a simple immutable point constructor
function:
var Point = function(_x, _y) {
this.x = function() {return _x;};
this.y = function() {return _y;};
this.distance = function(point) {
var dx = point.x() - _x;
var dy = point.y() - _y;
return Math.sqrt(dx * dx + dy * dy);
};
this.move = function(dx, dy) {
return new Point(_x + dx, _y + dy);
};
this.toString = function() {
return 'Point(' + _x + ', ' + _y + ')';
}
};
It would be used like this:
var p0 = new Point(1, 6);
p0.toString(); //=> Point(1, 6)
var p1 = p0.move(3, 4);
p1.toString(); //=> Point(4, 10)
p0.distance(p1); //=> 5
But when I use Cycle and simulate passing this over the wire (via
JSON methods), I get this behavior:
var points = Cycler.retrocycle(JSON.parse(
JSON.stringify(Cycler.decycle([p1, p2]))
));
points[0].toString(); //=> "[object Object]"
points[0].constructor; //=> function Point()
points[0].x(); //=> Type Error: points[0].x is not a function
This did not surprise me. I would have been quite surprised in fact
if you'd managed to make this work, as the internal data of a Point
is quite well encapsulated. There should be no way for your code to
access that closure. The advantage of the reviver paradigm in JSON,
as obnoxious as it is, is that it forces the user to consider this
up front in working with data that actually needs to be serialized.
What did surprise me, though, is that Cycle seems to have a problem
even with basic arrays:
points instanceof Array; //=> true
(so it says, but...)
points.map(function() {return "foo";}); //=> []
The data returned is not actually a proper array. This has nothing
to do with our Points:
var baz = Cycler.retrocycle(JSON.parse(
JSON.stringify(Cycler.decycle({name: 'a', vals: ['foo', 'bar']}))
));
baz.name; //=> 'a' // good!
baz.vals.map(function(val) {return val;}); //=> [] // uh oh!
> We are wondering about other light weight
> schemaless approaches. We explored adding a special metadata sub object to
> all serialized JSON objects, it just seemed a little less clean than what
> we ended up with in cycler.js since there was then a tendency to start
> "polluting" the metadata sub-object with all kinds of potentially application
> specific stuff, e.g. version numbers, date/time stamps, etc.
Although I would welcome one, I'd be surprised to find a useful generic
version of this concept. I think the difficulties are fundamental.