;; Copyright 2014-2018 Cognitect. 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 (:refer-clojure :exclude [integer? uuid uuid? uri?]) (:require [com.cognitect.transit :as t] [com.cognitect.transit.types :as ty] [com.cognitect.transit.eq :as eq]) (:import [goog.math Long])) ;; patch cljs.core/UUID IEquiv (extend-type UUID IEquiv (-equiv [this other] (cond (instance? UUID other) (identical? (.-uuid this) (.-uuid other)) (instance? ty/UUID other) (identical? (.-uuid this) (.toString other)) :else false))) (extend-protocol IComparable UUID (-compare [this other] (if (or (instance? UUID other) (instance? ty/UUID other)) (compare (.toString this) (.toString other)) (throw (js/Error. (str "Cannot compare " this " to " other))))) ty/UUID (-compare [this other] (if (or (instance? UUID other) (instance? ty/UUID other)) (compare (.toString this) (.toString other)) (throw (js/Error. (str "Cannot compare " this " to " other)))))) (extend-protocol IEquiv Long (-equiv [this other] (.equiv this other)) ty/UUID (-equiv [this other] (if (instance? UUID other) (-equiv other this) (.equiv this other))) ty/TaggedValue (-equiv [this other] (.equiv this other))) (extend-protocol IHash Long (-hash [this] (eq/hashCode this)) ty/UUID (-hash [this] (hash (.toString this))) ty/TaggedValue (-hash [this] (eq/hashCode this))) (extend-type ty/UUID IPrintWithWriter (-pr-writer [uuid writer _] (-write writer (str "#uuid \"" (.toString uuid) "\"")))) (defn ^:no-doc opts-merge [a b] (doseq [k (js-keys b)] (let [v (aget b k)] (aset a k v))) a) (deftype ^:no-doc MapBuilder [] Object (init [_ node] (transient {})) (add [_ m k v node] (assoc! m k v)) (finalize [_ m node] (persistent! m)) (fromArray [_ arr node] (cljs.core/PersistentArrayMap.fromArray arr true true))) (deftype ^:no-doc VectorBuilder [] Object (init [_ node] (transient [])) (add [_ v x node] (conj! v x)) (finalize [_ v node] (persistent! v)) (fromArray [_ arr node] (cljs.core/PersistentVector.fromArray arr true))) (defn reader "Return a transit reader. type may be either :json or :json-verbose. opts may be a map optionally containing a :handlers entry. The value of :handlers should be map from tag to a decoder function which returns then in-memory representation of the semantic transit value." ([type] (reader type nil)) ([type opts] (t/reader (name type) (opts-merge #js {:handlers (clj->js (merge {"$" (fn [v] (symbol v)) ":" (fn [v] (keyword v)) "set" (fn [v] (into #{} v)) "list" (fn [v] (into () (.reverse v))) "cmap" (fn [v] (loop [i 0 ret (transient {})] (if (< i (alength v)) (recur (+ i 2) (assoc! ret (aget v i) (aget v (inc i)))) (persistent! ret)))) "with-meta" (fn [v] (with-meta (aget v 0) (aget v 1)))} (:handlers opts))) :mapBuilder (MapBuilder.) :arrayBuilder (VectorBuilder.) :prefersStrings false} (clj->js (dissoc opts :handlers)))))) (defn read "Read a transit encoded string into ClojureScript values given a transit reader." [r str] (.read r str)) (deftype ^:no-doc KeywordHandler [] Object (tag [_ v] ":") (rep [_ v] (.-fqn v)) (stringRep [_ v] (.-fqn v))) (deftype ^:no-doc SymbolHandler [] Object (tag [_ v] "$") (rep [_ v] (.-str v)) (stringRep [_ v] (.-str v))) (deftype ^:no-doc ListHandler [] Object (tag [_ v] "list") (rep [_ v] (let [ret #js []] (doseq [x v] (.push ret x)) (t/tagged "array" ret))) (stringRep [_ v] nil)) (deftype ^:no-doc MapHandler [] Object (tag [_ v] "map") (rep [_ v] v) (stringRep [_ v] nil)) (deftype ^:no-doc SetHandler [] Object (tag [_ v] "set") (rep [_ v] (let [ret #js []] (doseq [x v] (.push ret x)) (t/tagged "array" ret))) (stringRep [v] nil)) (deftype ^:no-doc VectorHandler [] Object (tag [_ v] "array") (rep [_ v] (let [ret #js []] (doseq [x v] (.push ret x)) ret)) (stringRep [_ v] nil)) (deftype ^:no-doc UUIDHandler [] Object (tag [_ v] "u") (rep [_ v] (.-uuid v)) (stringRep [this v] (.rep this v))) (deftype ^:no-doc WithMeta [value meta]) (deftype ^:no-doc WithMetaHandler [] Object (tag [_ v] "with-meta") (rep [_ v] (t/tagged "array" #js [(.-value v) (.-meta v)])) (stringRep [_ v] nil)) (defn writer "Return a transit writer. type maybe either :json or :json-verbose. opts is a map with the following optional keys: :handlers - a map of type constructors to handler instances. :transform - a function of one argument returning a transformed value. Will be invoked on a value before it is written." ([type] (writer type nil)) ([type opts] (let [keyword-handler (KeywordHandler.) symbol-handler (SymbolHandler.) list-handler (ListHandler.) map-handler (MapHandler.) set-handler (SetHandler.) vector-handler (VectorHandler.) uuid-handler (UUIDHandler.) meta-handler (WithMetaHandler.) handlers (merge {cljs.core/Keyword keyword-handler cljs.core/Symbol symbol-handler cljs.core/Range list-handler cljs.core/List list-handler cljs.core/Cons list-handler cljs.core/EmptyList list-handler cljs.core/LazySeq list-handler cljs.core/RSeq list-handler cljs.core/IndexedSeq list-handler cljs.core/ChunkedCons list-handler cljs.core/ChunkedSeq list-handler cljs.core/PersistentQueueSeq list-handler cljs.core/PersistentQueue list-handler cljs.core/PersistentArrayMapSeq list-handler cljs.core/PersistentTreeMapSeq list-handler cljs.core/NodeSeq list-handler cljs.core/ArrayNodeSeq list-handler cljs.core/KeySeq list-handler cljs.core/ValSeq list-handler cljs.core/PersistentArrayMap map-handler cljs.core/PersistentHashMap map-handler cljs.core/PersistentTreeMap map-handler cljs.core/PersistentHashSet set-handler cljs.core/PersistentTreeSet set-handler cljs.core/PersistentVector vector-handler cljs.core/Subvec vector-handler cljs.core/UUID uuid-handler WithMeta meta-handler} (when (exists? cljs.core/Eduction) {^:cljs.analyzer/no-resolve cljs.core/Eduction list-handler}) (when (exists? cljs.core/Repeat) {^:cljs.analyzer/no-resolve cljs.core/Repeat list-handler}) (when (exists? cljs.core/MapEntry) {^:cljs.analyzer/no-resolve cljs.core/MapEntry vector-handler}) (:handlers opts))] (t/writer (name type) (opts-merge #js {:objectBuilder (fn [m kfn vfn] (reduce-kv (fn [obj k v] (doto obj (.push (kfn k) (vfn v)))) #js ["^ "] m)) :handlers (specify handlers Object (forEach ([coll f] (doseq [[k v] coll] (f v k))))) :unpack (fn [x] (if (instance? cljs.core/PersistentArrayMap x) (.-arr x) false))} (clj->js (dissoc opts :handlers))))))) (defn write "Encode an object into a transit string given a transit writer." [w o] (.write w o)) (defn read-handler "Construct a read handler. Implemented as identity, exists primarily for API compatiblity with transit-clj" [from-rep] from-rep) (defn write-handler "Creates a transit write handler whose tag, rep, stringRep, and verboseWriteHandler methods invoke the provided fns." ([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] (reify Object (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)))))) ;; ============================================================================= ;; Constructors & Predicates (defn tagged-value "Construct a tagged value. tag must be a string and rep can be any transit encodeable value." [tag rep] (ty/taggedValue tag rep)) (defn tagged-value? "Returns true if x is a transit tagged value, false otherwise." [x] (ty/isTaggedValue x)) (defn integer "Construct a transit integer value. Returns JavaScript number if in the 53bit integer range, a goog.math.Long instance if above. s may be a string or a JavaScript number." [s] (ty/intValue s)) (defn integer? "Returns true if x is an integer value between the 53bit and 64bit range, false otherwise." [x] (ty/isInteger x)) (defn bigint "Construct a big integer from a string." [s] (ty/bigInteger s)) (defn bigint? "Returns true if x is a transit big integer value, false otherwise." [x] (ty/isBigInteger x)) (defn bigdec "Construct a big decimal from a string." [s] (ty/bigDecimalValue s)) (defn bigdec? "Returns true if x is a transit big decimal value, false otherwise." [x] (ty/isBigDecimal x)) (defn uri "Construct a URI from a string." [s] (ty/uri s)) (defn uri? "Returns true if x is a transit URI value, false otherwise." [x] (ty/isURI x)) (defn uuid "Construct a UUID from a string." [s] (ty/uuid s)) (defn uuid? "Returns true if x is a transit UUID value, false otherwise." [x] (or (ty/isUUID x) (instance? UUID x))) (defn binary "Construct a transit binary value. s should be base64 encoded string." [s] (ty/binary s)) (defn binary? "Returns true if x is a transit binary value, false otherwise." [x] (ty/isBinary x)) (defn quoted "Construct a quoted transit value. x should be a transit encodeable value." [x] (ty/quoted x)) (defn quoted? "Returns true if x is a transit quoted value, false otherwise." [x] (ty/isQuoted x)) (defn link "Construct a transit link value. x should be an IMap instance containing at a minimum the following keys: :href, :rel. It may optionall include :name, :render, and :prompt. :href must be a transit URI, all other values are strings, and :render must be either :image or :link." [x] (ty/link x)) (defn link? "Returns true if x a transit link value, false if otherwise." [x] (ty/isLink x)) (defn write-meta "For :transform. Will write any metadata present on the value." [x] (if (implements? IMeta x) (let [m (-meta ^not-native x)] (if-not (nil? m) (WithMeta. (-with-meta ^not-native x nil) m) x)) x))