Transit format: An interactive tutorial - better than JSON (part 1)

What is Transit?

Transit is a format and set of libraries for conveying values between applications written in different programming languages.

Transit provides:

a set of basic elements

a set of extension elements for representing typed values.

The extension mechanism is open, allowing programs using Transit to add new elements specific to their needs. It constrats with traditional formats where usually users of data formats must rely on one of the following:

schemas

convention

context

to convey elements not included in the base set, making application code much more complex.

Transit is designed to be implemented as an encoding on top of formats for which high performance processors already exist, specifically JSON and MessagePack . Transit uses these formats’ native representations for built-in elements, e.g., strings and arrays, wherever possible. Extension elements which have no native representation in these formats, e.g., dates, are represented using a tag-based encoding scheme. It makes Transit a self-describing and extensible format.

Read more about Transit.

Code examples please

All the above is very very abstract. Let’s code some examples.

In this article, we are going to use transit-cljs to illustrate Transit format.

As usual, the code snippets are interactive - powered by the KLIPSE plugin. Feel free to modify the code in order to get a better feeling of the concepts.

First, let’s require transit :

(ns my.transit (:require [cognitect.transit :as t]))

Now, we can play with transit …

Let’s create a JSON reader and writer:

(def r (t/reader :json)) (def w (t/writer :json))

And let’s see them in action:

Reader - basics

The reader receives a string in json format, and returns a clojure object:

Any type supported in json can go in…

Arrays, numbers, unicode strings, booleans, null :

(t/read r "[1, \"2\", null, true, \"\u03BB\"]")

and obviously objects:

(t/read r "{\"a\": 1, \"b\": [1, 2.2, 2e5, null]}")

Writer - basics

The writer is the opposite of the writer: it receives a clojure object and returns a string in json format:

Let’s write a clojure array:

(t/write w [1 1.2e4 "2" nil true])

And a clojure object:

(t/write w {"a" 1, "b" [1 2.2 200000 nil]})

Additional types

The interest of transit is that it supports additional types that are not supported in JSON like: keywords, symbols, dates, sets, lists:

Let’s see how keywords are encoded:

(t/write w [:aaa :bbb :my.ns/bbb ::aaa])

Symbols:

(t/write w ['aa 'bb])

Dates:

(t/write w [(js/Date) #inst "2016-09-22T18:27:18.001-00:00"])

Sets:

(t/write w #{1 2 3})

UUIDs:

(t/write w (random-uuid))

Each of the above strings can be decoded back into a clojure object with the writer:

(t/read r (t/write w [(js/Date) #inst "2016-09-22T18:27:18.001-00:00"]))

(t/read r (t/write w #{1 2 3}))

Any keys in an object

JSON suports only strings in keys. However in clojure , any type can be a key in a map . It means that we cannot truly serialize a clojure map. Usually, the trick is to convert types into strings. Like this:

(def array-key-map {[1 2] "cool"}) (-> array-key-map clj->js js/JSON.stringify)

But when we read the string back, we don’t get the original object:

(-> array-key-map clj->js js/JSON.stringify js/JSON.parse js->clj)

Transit solves this problem by encoding the types:

(t/write w array-key-map)

And the roundtrip is safe:

(t/read r (t/write w array-key-map))