-
Notifications
You must be signed in to change notification settings - Fork 21
Getting Started
In order to read Transit encoded JSON you need to construct a reader:
(ns try-transit
(:require [cognitect.transit :as t]))
(def r (t/reader :json))
Currently :json
is the only type of reader available. Given
a reader you can invoke cognitect.transit/read
.
For example reading a vector:
(def v (t/read r "[1,2,3]")) ;; [1 2 3]
And reading a map:
(def m (t/read r "{\"~:foo\":\"bar\"}")) ;; {:foo "bar"}
Like reading, writing is fairly straightforward. Constructing a writer looks very much like constructing a reader:
(ns try-transit
(:require [cognitect.transit :as t]))
(def w (t/writer :json))
Once you've constructed a writer you can invoke
cognitect.transit/write
to encode values.
For example writing a vector:
(t/write w [1 2 3]) ;; "[1,2,3]"
And writing a map:
(t/write w {:foo "bar"}) ;; "[\"^ \",\"~:foo\",\"bar\"]"
Maps get written out as JSON arrays as this form is more efficient for decoding. For debugging purposes it's useful to construct a verbose writer:
(def wv (t/writer :json-verbose))
And now the result of writing map-like values is easier to read:
(t/write wv {:foo "bar"}) ;; "{\"~:foo\":\"bar\"}"
Being able to easily write out graphs of ClojureScript values is one the big benefits of transit-cljs. transit-cljs will recursively encode graphs of values and Transit ground values like integers and dates need no special treatment.
To demonstrate this lets define some simple geometry primitives:
(defrecord Rect [origin size])
(defrecord Point [x y])
(defrecord Size [width height])
(def arect (Rect. (Point. 0 0) (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
:
(deftype PointHandler []
Object
(tag [this v] "point")
(rep [this v] #js [(.-x v) (.-y v)])
(stringRep [this v] nil))
Write handlers are simplest to write with deftype
. 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 receives the
value v
to be written.
tag
should be a method that will take the value and return a
string based tag. You can of course use the value 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-cljs 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:
(def w
(t/writer :json-verbose
{:handlers {Point (PointHandler.)}}))
(t/write w (Point. 1.5 2.5)) ;; => "{\"~#point\":[1.5,2.5]}"
Now let's write the handlers for Size
and Rect
:
(deftype SizeHandler []
Object
(tag [this v] "size")
(rep [this v] #js [(.-width v) (.-height v)])
(stringRep [this v] nil))
(deftype RectHandler []
Object
(tag [this v] "rect")
(rep [this v] #js [(.-origin v) (.-size v)])
(stringRep [this v] nil))
That's it, we can now write out Rect
instances!
(def w
(t/writer :json-verbose
{:handlers
{Point (PointHandler.)
Size (SizeHandler.)
Rect (RectHandler.)}}))
(t/write w arect)
;; => "{\"~#rect\":[{\"~#point\":[0,0]},{\"~#size\":[150,150]}]}"
Now that we can write custom types we will want to be able read them.
(def r
(t/reader :json
{:handlers
{"point" (fn [[x y]] (Point. x y))
"size" (fn [[width height]] (Size. width height))
"rect" (fn [[origin size]] (Rect. origin size))}}))
(t/read r "{\"~#rect\":[{\"~#point\":[0,0]},{\"~#size\":[150,150]}]}")
;; => #try-transit.Rect{:origin #try-transit.Point{:x 0, :y 0},
;; :size #try-transit.Size{:width 150, :height 150}}
Reading is considerably simpler. When a tagged value is encountered the corresponding handler is invoked with the representation 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-cljs is recursive and these will have already
been instantiated for you.