
cognitect.transit.clj Maven / Gradle / Ivy
;; Copyright 2014 Rich Hickey. All Rights Reserved.
;;
;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
;; You may obtain a copy of the License at
;;
;; http://www.apache.org/licenses/LICENSE-2.0
;;
;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS-IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.
(ns cognitect.transit
"An implementation of the transit-format for Clojure built
on top of the transit-java library."
(:refer-clojure :exclude [read])
(:require [clojure.string :as str])
(:import [com.cognitect.transit WriteHandler ReadHandler ArrayReadHandler MapReadHandler
ArrayReader TransitFactory TransitFactory$Format MapReader]
[com.cognitect.transit.SPI ReaderSPI]
[java.io InputStream OutputStream]))
;; writing
(set! *warn-on-reflection* true)
(defn- transit-format
"Converts a keyword to a TransitFactory$Format value."
[kw]
(TransitFactory$Format/valueOf
(str/join "_" (-> kw
name
str/upper-case
(str/split #"-")))))
(defn tagged-value
"Creates a TaggedValue object."
[tag rep] (TransitFactory/taggedValue tag rep))
(defn nsed-name
"Convert a keyword or symbol to a string in
namespace/name format."
[^clojure.lang.Named kw-or-sym]
(if-let [ns (.getNamespace kw-or-sym)]
(str ns "/" (.getName kw-or-sym))
(.getName kw-or-sym)))
(defn- fn-or-val
[f]
(if (fn? f) f (constantly f)))
(defn write-handler
"Creates a transit WriteHandler whose tag, rep,
stringRep, and verboseWriteHandler methods
invoke the provided fns.
If a non-fn is passed as an argument, implemented
handler method returns the value unaltered."
([tag-fn rep-fn]
(write-handler tag-fn rep-fn nil nil))
([tag-fn rep-fn str-rep-fn]
(write-handler tag-fn rep-fn str-rep-fn nil))
([tag-fn rep-fn str-rep-fn verbose-handler-fn]
(let [tag-fn (fn-or-val tag-fn)
rep-fn (fn-or-val rep-fn)
str-rep-fn (fn-or-val str-rep-fn)
verbose-handler-fn (fn-or-val verbose-handler-fn)]
(reify WriteHandler
(tag [_ o] (tag-fn o))
(rep [_ o] (rep-fn o))
(stringRep [_ o] (when str-rep-fn (str-rep-fn o)))
(getVerboseHandler [_] (when verbose-handler-fn (verbose-handler-fn)))))))
(defn default-write-handlers
"Returns a map of default WriteHandlers for
Clojure types. Java types are handled
by the default WriteHandlers provided by the
transit-java library."
[]
{
java.util.List
(reify WriteHandler
(tag [_ l] (if (seq? l) "list" "array"))
(rep [_ l] (if (seq? l) (TransitFactory/taggedValue "array" l ) l))
(stringRep [_ _] nil)
(getVerboseHandler [_] nil))
clojure.lang.BigInt
(reify WriteHandler
(tag [_ _] "n")
(rep [_ bi] (str (biginteger bi)))
(stringRep [this bi] (.rep this bi))
(getVerboseHandler [_] nil))
clojure.lang.Keyword
(reify WriteHandler
(tag [_ _] ":")
(rep [_ kw] (nsed-name kw))
(stringRep [_ kw] (nsed-name kw))
(getVerboseHandler [_] nil))
clojure.lang.Ratio
(reify WriteHandler
(tag [_ _] "ratio")
(rep [_ r] (TransitFactory/taggedValue "array" [(numerator r) (denominator r)]))
(stringRep [_ _] nil)
(getVerboseHandler [_] nil))
clojure.lang.Symbol
(reify WriteHandler
(tag [_ _] "$")
(rep [_ sym] (nsed-name sym))
(stringRep [_ sym] (nsed-name sym))
(getVerboseHandler [_] nil))
})
(deftype Writer [w])
(defn writer
"Creates a writer over the privided destination `out` using
the specified format, one of: :msgpack, :json or :json-verbose.
An optional opts map may be passed. Supported options are:
:handlers - a map of types to WriteHandler instances, they are merged
with the default-handlers and then with the default handlers
provided by transit-java."
([out type] (writer out type {}))
([^OutputStream out type opts]
(if (#{:json :json-verbose :msgpack} type)
(let [handlers (merge (default-write-handlers) (:handlers opts))]
(Writer. (TransitFactory/writer (transit-format type) out handlers)))
(throw (ex-info "Type must be :json, :json-verbose or :msgpack" {:type type})))))
(defn write
"Writes a value to a transit writer."
[^Writer writer o]
(.write ^com.cognitect.transit.Writer (.w writer) o))
;; reading
(defn read-handler
"Creates a transit ReadHandler whose fromRep
method invokes the provided fn."
[from-rep]
(reify ReadHandler
(fromRep [_ o] (from-rep o))))
(defn read-map-handler
"Creates a Transit MapReadHandler whose fromRep
and mapReader methods invoke the provided fns."
[from-rep map-reader]
(reify MapReadHandler
(fromRep [_ o] (from-rep o))
(mapReader [_] (map-reader))))
(defn read-array-handler
"Creates a Transit ArrayReadHandler whose fromRep
and arrayReader methods invoke the provided fns."
[from-rep array-reader]
(reify ArrayReadHandler
(fromRep [_ o] (from-rep o))
(arrayReader [_] (array-reader))))
(defn default-read-handlers
"Returns a map of default ReadHandlers for
Clojure types. Java types are handled
by the default ReadHandlers provided by the
transit-java library."
[]
{":"
(reify ReadHandler
(fromRep [_ o] (keyword o)))
"$"
(reify ReadHandler
(fromRep [_ o] (symbol o)))
"ratio"
(reify ReadHandler
(fromRep [_ o] (/ (.get ^java.util.List o 0)
(.get ^java.util.List o 1))))
"n"
(reify ReadHandler
(fromRep [_ o] (clojure.lang.BigInt/fromBigInteger
(BigInteger. ^String o))))
"set"
(reify ArrayReadHandler
(fromRep [_ o] o)
(arrayReader [_]
(reify ArrayReader
(init [_] (transient #{}))
(init [_ ^int size] (transient #{}))
(add [_ s item] (conj! s item))
(complete [_ s] (persistent! s)))))
"list"
(reify ArrayReadHandler
(fromRep [_ o] o)
(arrayReader [_]
(reify ArrayReader
(init [_] (java.util.ArrayList.))
(init [_ ^int size] (java.util.ArrayList. size))
(add [_ l item] (.add ^java.util.List l item) l)
(complete [_ l] (or (seq l) '())))))
"cmap"
(reify ArrayReadHandler
(fromRep [_ o] o)
(arrayReader [_]
(let [marker (Object.)
^objects next-key (object-array [marker])]
(reify ArrayReader
(init [_] (transient {}))
(init [_ ^int size] (transient {}))
(add [_ m item]
(let [k (aget next-key 0)]
(if (identical? k marker)
(do
(aset next-key 0 item)
m)
(do
(aset next-key 0 marker)
(assoc! m k item)))))
(complete [_ m] (persistent! m))))))})
(defn map-builder
"Creates a MapBuilder that makes Clojure-
compatible maps."
[]
(reify MapReader
(init [_] (transient {}))
(init [_ ^int size] (transient {}))
(add [_ m k v] (assoc! m k v))
(complete [_ m] (persistent! m))))
(defn list-builder
[]
"Creates an ArrayBuilder that makes Clojure-
compatible lists."
(reify ArrayReader
(init [_] (transient []))
(init [_ ^int size] (transient []))
(add [_ v item] (conj! v item))
(complete [_ v] (persistent! v))))
(deftype Reader [r])
(defn reader
"Creates a reader over the provided source `in` using
the specified format, one of: :msgpack, :json or :json-verbose.
An optional opts map may be passed. Supported options are:
:handlers - a map of tags to ReadHandler instances, they are merged
with the Clojure default-read-handlers and then with the default ReadHandlers
provided by transit-java.
:default-handler - an instance of DefaultReadHandler, used to process
transit encoded values for which there is no other ReadHandler; if
:default-handler is not specified, non-readable values are returned
as TaggedValues."
([in type] (reader in type {}))
([^InputStream in type opts]
(if (#{:json :json-verbose :msgpack} type)
(let [handlers (merge (default-read-handlers) (:handlers opts))
default-handler (:default-handler opts)
reader (TransitFactory/reader (transit-format type)
in
handlers
default-handler)]
(Reader. (.setBuilders ^ReaderSPI reader
(map-builder)
(list-builder))))
(throw (ex-info "Type must be :json, :json-verbose or :msgpack" {:type type})))))
(defn read
"Reads a value from a reader."
[^Reader reader]
(.read ^com.cognitect.transit.Reader (.r reader)))
(defn record-write-handler
"Creates a WriteHandler for a record type"
[^Class type]
(reify WriteHandler
(tag [_ _] (.getName type))
(rep [_ rec] (tagged-value "map" rec))
(stringRep [_ _] nil)
(getVerboseHandler [_] nil)))
(defn record-write-handlers
"Creates a map of record types to WriteHandlers"
[& types]
(reduce (fn [h t] (assoc h t (record-write-handler t)))
{}
types))
(defn record-read-handler
"Creates a ReadHandler for a record type"
[^Class type]
(let [type-name (map #(str/replace % "_" "-") (str/split (.getName type) #"\."))
map-ctor (-> (str (str/join "." (butlast type-name)) "/map->" (last type-name))
symbol
resolve)]
(reify ReadHandler
(fromRep [_ m] (map-ctor m)))))
(defn record-read-handlers
"Creates a map of record type tags to ReadHandlers"
[& types]
(reduce (fn [d ^Class t] (assoc d (.getName t) (record-read-handler t)))
{}
types))
(comment
(require 'cognitect.transit)
(in-ns 'cognitect.transit)
(import [java.io File ByteArrayInputStream ByteArrayOutputStream OutputStreamWriter])
(def out (ByteArrayOutputStream. 2000))
(def w (writer out :json))
(def w (writer out :json-verbose))
(def w (writer out :msgpack))
(write w "foo")
(write w 10)
(write w {:a-key 1 :b-key 2})
(write w {"a" "1" "b" "2"})
(write w {:a-key [1 2]})
(write w #{1 2})
(write w [{:a-key 1} {:a-key 2}])
(write w [#{1 2} #{1 2}])
(write w (int-array (range 10)))
(write w {[:a :b] 2})
(write w [123N])
(write w 1/3)
(write w {false 10 [] 20})
(def in (ByteArrayInputStream. (.toByteArray out)))
(def r (reader in :json))
(def r (reader in :msgpack))
(read r)
(type (read r))
;; extensibility
(defrecord Point [x y])
(defrecord Circle [c r])
(def ext-write-handlers
{Point
(write-handler "point" (fn [p] [(.x p) (.y p)]))
Circle
(write-handler "circle" (fn [c] [(.c c) (.r c)]))})
(def ext-read-handlers
{"point"
(read-handler (fn [[x y]] (prn "making a point") (Point. x y)))
"circle"
(read-handler (fn [[c r]] (prn "making a circle") (Circle. c r)))})
(def ext-write-handlers
(record-write-handlers Point Circle))
(def ext-read-handlers
(record-read-handlers Point Circle))
(def out (ByteArrayOutputStream. 2000))
(def w (writer out :json {:handlers ext-write-handlers}))
(write w (Point. 10 20))
(write w (Circle. (Point. 10 20) 30))
(write w [(Point. 10 20) (Point. 20 40) (Point. 0 0)])
(def in (ByteArrayInputStream. (.toByteArray out)))
(def r (reader in :json {:handlers ext-read-handlers}))
(read r)
)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy