Caching in transit-cljs
Tuesday, March 17, 2015 at 3:09PM
Stuart Mitchell

So I've been doing alot of coding in clojurescript lately and I had to do some work with transit-cljs.

One of the things I had to test was if I could do comprehensive caching and de-duping of objects serialized with transit-cljs. I based my tests on transit-js-caching but this article has an example in javascript and not clojurescript. So I needed to convert it. Here is how I did it but you will need to read the previous article for the motivation of each step.

1) include the transit-cljs library in your code

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

2) define an Object in clojurescript

 (defrecord Point [x y])

you can also use deftype but defrecord provides more helper functions like equality etc

3) define a custom write handler with transit-cljs and a function that uses it

 (def PointHandler 
   (t/write-handler (fn [v, h] "point") (fn [v, h] [v.x v.y])))

 (def writer (t/writer "json" {:handlers {Point PointHandler}}))

 (defn write [x]
   (t/write writer x))

3) use this writing function

 (def p (Point. 1 2))

 (print (write p))
 (print (write [p p p p]))

gives

 "[\"~#point\",[1,2]]"
 "[[\"~#point\",[1,2]],[\"^0\",[1,2]],
  [\"^0\",[1,2]],[\"^0\",[1,2]]]"

see by default transit-cljs caches the keys in the JSON

4) create a custom reader

 (defn read [x]
    (t/read (t/reader "json" 
                      {:handlers {"point" 
                                 (fn [v] 
                                     (Point. (aget v 0) 
                                             (aget v 1)))}})
            x))

5) now we can do a roundtrip!

 (print (= p (read (write p))))
 (print (= [p p p p] (read (write [p p p p]))))

 true
 true

6) How do we write a caching writer

 (defn caching-point-handler
   ([] (caching-point-handler (atom {})))
   ([cache]
    (t/write-handler 
      (fn [v, h] (if (get @cache v) 
                   "cache" 
                   "point")) 
      (fn [v, h] (let [id (get @cache v)] 
                   (if (nil? id)
                     (do 
                       (swap! cache 
                              #(assoc % v (count %)))
                       [v.x v.y])
                     id))))))

 (defn c-writer 
   []
   (t/writer "json" {:handlers {Point (caching-point-handler)}}))

 (defn c-write [x]
   (t/write (c-writer) x))

7) what does this produce

 (print (c-write p))
 (print (c-write [p p p p]))

 "[\"~#point\",[1,2]]"
 "[[\"~#point\",[1,2]],[\"~#cache\",0],
  [\"^1\",0],[\"^1\",0]]"

note that the second item is cached and the "cache" keyword is replaced by ^1 in the third item

8) what does the reader look like

 (defn c-read [x]
   (let [cache (atom {})] 
     (t/read (t/reader "json" 
                       {:handlers {"point" 
                                   (fn [v]
                                     (let [point (Point. 
                                                    (aget v 0) 
                                                    (aget v 1))]
                                       (swap! cache 
                                              #(assoc % 
                                                (count %) point))
                                       point))
                                   "cache"
                                   (fn [v]
                                     (get @cache v))}})
             x)))

9) now we can roundtrip

 (print (= p (c-read (c-write p))))
 (print (= [p p p p] (c-read (c-write [p p p p]))))))

 true
 true
Article originally appeared on Stuart Mitchell Consulting (http://www.stuartmitchell.com/).
See website for complete article licensing information.