Skip to content
David Nolen edited this page Aug 9, 2014 · 38 revisions

Reading

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");

Writing

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"}'.

Writing Custom Values

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 recieve 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 that JavaScript objects cannot support constructors as 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 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.

Reading Custom Types

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.

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.

Avoiding duplication

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.

Clone this wiki locally