-
Notifications
You must be signed in to change notification settings - Fork 31
Getting Started
In order to read Transit encoded JSON you need to construct a reader:
var reader = transit.reader("json");
Currently "json"
is the only type of reader available. The above
will construct a reader instance which has only one method, read
:
var anArray = reader.read("[1,2,3]");
Most JSON values will read as you expect but remember that the map encoding returns ES6-like Maps not JavaScript objects. The primary difference with ES6 Maps is that lookup is based on value not reference:
var aMap = reader.read('{"foo":"bar"}');
aMap.get("foo");
Like reading, writing is fairly straightforward. Constructing a writer looks very much like constructing a reader:
var writer = transit.writer("json");
Writers only have a single method, write
:
writer.write([1,2,3]);
This will return the string '[1,2,3]'
as expected.
Compare to writing map-like values:
writer.write({foo:"bar"});
This will return the string'["^ ","foo","bar"]'
. Maps get written out as arrays
as this form is more efficient for decoding. For debugging purposes
it's useful to construct a verbose writer:
var vwriter = transit.writer("json-verbose");
And now the result of writing map-like values is easier to read:
vwriter.write({foo:"bar"});
This will return the string '{"foo":"bar"}'
.
Being able to easily write out graphs of JavaScript objects is one the big benefits of transit-js. transit-js will recursively encode graphs of objects and Transit ground values like integers and dates need no special treatment.
To demonstrate this lets define some simple geometry primitives:
var Rect = function(origin, size) {
this.origin = origin;
this.size = size;
};
var Point = function(x, y) {
this.x = x;
this.y = y;
};
var Size = function(width, height) {
this.width = width;
this.height = height;
};
var aRect = new Rect(new Point(0, 0), new Size(150,150));
In order to write out aRect
we need write handlers for all
of the types involved. First let's write a handler
for Point
:
var PointHandler = transit.makeWriteHandler({
tag: function(v, h) { return "point" },
rep: function(v, h) { return [v.x, v.y]; },
stringRep: function(v, h) { return null; }
});
Write handlers are constructed with transit.makeWriteHandler
. Custom
types always become tagged values on the wire and the handler methods
specify how your instance will become a Transit tagged
value. Write handlers must supply at least the first two of the three methods: tag
,
rep
, and stringRep
. Each handler method recieves the instance v
as
the first argument, and the handler h
itself as the second argument.
tag
should be a function that will take the instance and return a
string based tag. You can of course use the instance argument v
to
write out different tags if you like but we're going to keep it simple
here.
rep
is the representation to use for the tagged value. In this
case we simply return an array containing the x
and y
properties. These properties are numbers, a ground type, so
there's nothing more for us to do. It's important that the result
of rep
be something that transit-js already knows how to encode
either via a built-in or provided custom handler.
stringRep
is for tagged values that have a sensible representation as
JSON object keys (strings). For the most part you can omit
this method but we've left it here for completeness.
Now we can construct the following verbose writer and write Point
instances:
var vwriter = transit.writer("json-verbose", {
"handlers": transit.map([
Point, PointHandler
])
});
vwriter.write(new Point(1.5,2.5));
This will return '{"~#point:[1.5,2.5]"}'
. Notice that we had to
pass the handlers as a transit.map
. This is because
JavaScript objects only support string keys.
Now let's write the handlers for Size
and Rect
:
var SizeHandler = transit.makeWriteHandler({
tag: function(v, h) { return "size"; },
rep: function(v, h) { return [v.width, v.height]; },
stringRep: function(v, h) { return null; }
});
var RectHandler = transit.makeWriteHandler({
tag: function(v, h) { return "rect"; },
rep: function(v, h) { return [v.origin, v.size]; },
stringRep: function(v, h) { return null; }
});
That's it, we can now write out Rect
instances!
var vwriter = transit.writer("json-verbose", {
"handlers": transit.map([
Point, PointHandler,
Size, SizeHandler,
Rect, RectHandler
])
});
vwriter.write(aRect);
This will return
'{"~#rect":[{"~#point":[0,0]},{"~#size":[150,150]}]}'
. As we said
earlier transit-js will recurse through the values returned by the
rep
methods and encode as needed.
Now that we can write custom types we will want to be able read them.
var reader = transit.reader("json", {
"handlers": {
"point": function(rep) { return new Point(v[0], v[1]); },
"size": function(rep) { return new Size(v[0], v[1]); },
"rect": function(rep) { return new Rect(v[0], v[1]); }
}
});
Reading is considerably simpler. When a tagged value is encountered the corresponding handler is invoked with the representaiton that was written on the wire - in our case we just used arrays (we could have used maps).
Notice that the Rect
handler doesn't need to instantiate Point
or
Size
. Again transit-js is recursive and these will have already
been instantiated for you.
In real applications you may find yourself with many different types
that share most of their representation (possibly through prototypal
inheritance). In this case it may be cumbersome to define a large
set of handlers. Via transitTag
you can easily reap the benefits
of inheritance.
For example you can in your base prototype declare a transitTag
and
give it whatever string value you want.
var BaseClass = function() {};
BaseClass.prototype.transitTag = "base";
Now when you create your handler you can leverage inheritance:
var MyWriteHandler = transit.makeWriteHandler({
tag: function(v) { return v.tag(); },
rep: function(v) { return v.rep(); }
});
var writer = transit.writer("json", {
"handlers": transit.map([
"base", MyWriteHandler
])
});
Instances are now completely in control of the tag
string and the
rep
value - implementations can be shared in whatever way best
fits your application.
On the read side because read handlers are generally very simple it's
easy to dynamically generate the reader handlers
option.
It is sometimes useful to be able to control the the decoding of Transit
maps and arrays. For this purpose transit-js supplies two low-level
options when constructing readers: mapBuilder
and arrayBuilder
. Builders
must supply at a minimum three methods: init
, add
, and finalize
. Builders
may also supply an optional fromArray
parameter in the case that efficient
construction is possible from an array representation.
In the following example we construct a reader that returns
immutable-js's Map
and
Vector
instances instead of transit-js maps and JavaScript arrays:
var rdr = transit.reader("json", {
arrayBuilder: {
init: function(node) { return Immutable.Vector().asMutable(); },
add: function(ret, val, node) { return ret.push(val); },
finalize: function(ret, node) { return ret.asImmutable(); },
fromArray: function(arr, node) {
return Immutable.Vector.from(arr);
}
},
mapBuilder: {
init: function(node) { return Immutable.Map().asMutable(); },
add: function(ret, key, val, node) { return ret.set(key, val); },
finalize: function(ret, node) { return ret.asImmutable(); }
}
});
It's also possible to encode values as Transit maps and arrays. We can
easily write out immutable-js Map
and Vector
as Transit maps and
arrays respectively:
var VectorHandler = transit.makeWriteHandler({
tag: function(v) { return "array"; },
rep: function(v) { return v; },
stringRep: function(v) { return null; }
});
var MapHandler = transit.makeWriteHandler({
tag: function(v) { return "map"; },
rep: function(v) { return v; },
stringRep: function(v) { return null; }
});
var wrtr = transit.writer("json-verbose", {
handlers: transit.map([
Immutable.Vector, VectorHandler,
Immutable.Map, MapHandler
])
});
The "array"
and "map"
tags are treated specially. Notice that for
the rep
handlers we can just return the instance. This is because
immutable-js collections implement forEach
. Without a forEach
implentation transit-js cannot traverse your custom map/array-like
collections to recursive encode their contents.