This tour introduces the Transit format via transit-js, an implementation of the Transit format for JavaScript.
Transit implementations for other languages may provide slightly different APIs that reflect the idioms of the implementation language and language specific performance concerns. However, regardless of implementation, the semantics of the format must be preserved.
The guide is interactive. Many of the code snippets may be evaluated by pressing the Eval button underneath the snippet. These examples are CodeMirror editors so that you can edit and try out the transit-js API youself:
Most Transit implementations support encodings in JSON, msgpack, or both, but transit-js currently only supports JSON. That said, you can specify what type of reader you wish to construct:
Once a reader has been constructed you can read
transit wire data. All JSON is valid wire data for
transit JSON readers. Some Transit read
implementations take an encoded input stream. In
the transit-js case read
simply takes
a string of encoded data:
We can read JSON strings, numbers, and arrays as supported by JSON.parse:
We can also read values not supported by
JSON.parse
. This is possible by encoding values as
strings and using a simple tag convention. Transit
JSON strings that represent a scalar value are
prefixed with
~
followed by single character. For
example the tag ~m
denotes a
non-verbose encoding of dates (human readable
dates can also be read/written).
The example above shows that the ~m
tag uses
the number of milliseconds after the date January 1, 1970 at 00:00:00
to encode a date value. For a full list of the scalar tags please
refer to the Transit
specification.
We've already seen that JSON arrays can be read,
but you can also read JSON objects. Note that
transit-js read
returns
hash maps when reading objects because
JavaScript objects cannot support
scalar values beyond strings as keys.
This means that you can't dereference properties using bracket notation, since they are not plain JavaScript objects:
While this may initially seem less convenient, there are benefits to opting into returning maps in transit-js.
transit-js hash maps support most of the proposed ECMAScript 6 Map interface, however look up is based on value instead of reference:
The above is impossible under the proposed ES6 Map specification.
transit-js supports reading and writing maps with richer keys including collection types like JavaScript objects, arrays, and transit-js maps. For example you can read a map with a date key:
transit-js maps and sets may be used à la carte. They have been written with performance in mind - they are competitive with plain JavaScript objects, they outperform existing ES6 Map/Set shims and they are comparable in many cases to pending native implementations of Map and Set:
Transit also supports non-scalar values beyond
arrays and maps via tagged values. Tagged values
are represented as a single key-value pair -
either a JSON object or a two element array. The
key is a tag - a string prefixed with
~#
. The value is a Transit value that
can be further interpreted by a user-supplied
tagged value handler, after being decoded by
Transit itself. Transit ships with several tagged
values built in including ES6-like sets:
Tagged values that have no user installed handler
will be handled by a default handler. This allows
sending data intact through heterogenous
independent systems that utilize Transit. For example
in the following no handler exists for line
.
Yet we can read it and if we write it back out it remains
intact.
This brings us to the topic of encoding transit values.
Creating a Transit writer is similar to constructing a reader:
We can serialize simple JavaScript values as we would with JSON.stringify:
We can also encode values not supported by JSON.stringify, for example dates and 64 bit integer values:
We can also encode map and map-like collection types:
Notice the map is written as a JavaScript array. While this may be more efficient it's also less readable. You can construct a verbose writer to get human readable output for debugging purposes:
One of the biggest drawbacks of JSON as a data format is the lack of extensibility. Transit allows users to customize both reading and writing.
Transit readers can be customized with user provided handlers. For example:
Transit writers can similarly be extended to handle custom types:
You may be wondering where the ^0
came from, this is an artifact of caching.
Transit implementations cache map keys, transit
symbols, transit keywords, and tagged value tags
on read and write. This simple optimization allows
Transit implementations to compete with
traditional JSON encoder/decoder usage -
transit-js read
can be faster
than JSON.parse
under some JavaScript
engines for the same logical data:
As the redundancy in the data grows, caching delivers increasingly compact results.
By clicking Time you can
compare JSON.parse
to
Transit read
on typical database
data. You can view the
JSON here and
the Transit
JSON here. Under Node.js and Chrome 36
transit-js read
slightly outperforms
JSON.parse
in this case. Opera 22,
Internet Explorer 11, and 10
transit-js read
is only slightly
slower than JSON.parse
. Note the
transit-js read
timings
include JSON.parse
in
addition to hydration. The second row includes the
cost of supplying
a JSON.parse
hydration (reviver) function that simply returns the value to give further
context.
Method | Size | Avg. over 100 iters (ms) |
---|---|---|
JSON.parse | 89K | |
JSON.parse w/ hydrate | 89K | |
JSON.parse Transit JSON | 53K | |
Transit read | 53K |
These numbers demonstrate that in several browsers
parsing a smaller amount of JSON data encoded as
arrays is often significantly faster and in the
saved time it is possible to hydrate keys and
values and compare favorably to JSON parsing of
plain JSON data. Even in the case of browsers like
Internet Explorer 9, Safari and Firefox,
transit-js read
compares quite well
to JSON.parse
supplied with a
hydration function (which of course does not
permit key hydration).