Skip to content
Permalink
Browse files

CLJS-410: add clj->js and make js->clj extendable

  • Loading branch information...
mpenet authored and David Nolen committed Nov 19, 2012
1 parent 8c8b1a9 commit cd66e6b9e63ad5ef1896a9c7a117148beb04301d
Showing with 78 additions and 15 deletions.
  1. +59 −14 src/cljs/cljs/core.cljs
  2. +19 −1 test/cljs/cljs/core_test.cljs
@@ -6844,24 +6844,70 @@ reduces them without incurring seq initialization"
[d]
(-realized? d))

(defprotocol IEncodeJS
(-clj->js [x] "Recursively transforms clj values to JavaScript")
(-key->js [x] "Transforms map keys to valid JavaScript keys. Arbitrary keys are
encoded to their string representation via (pr-str x)"))

(extend-protocol IEncodeJS
default
(-key->js [k]
(if (or (string? k)
(number? k)
(keyword? k)
(symbol? k))
(-clj->js k)
(pr-str k)))

(-clj->js [x]
(cond
(keyword? x) (name x)
(symbol? x) (str x)
(map? x) (let [m (js-obj)]
(doseq [[k v] x]
(aset m (-key->js k) (-clj->js v)))
m)
(coll? x) (apply array (map -clj->js x))
:else x))

nil
(-clj->js [x] nil))

(defn clj->js
"Recursively transforms ClojureScript values to JavaScript.
sets/vectors/lists become Arrays, Keywords and Symbol become Strings,
Maps become Objects. Arbitrary keys are encoded to by key->js."
[x]
(-clj->js x))

(defprotocol IEncodeClojure
(-js->clj [x] [x options] "Transforms JavaScript values to Clojure"))

(extend-protocol IEncodeClojure
default
(-js->clj
([x options]
(let [{:keys [keywordize-keys]} options
keyfn (if keywordize-keys keyword str)
f (fn thisfn [x]
(cond
(seq? x) (doall (map thisfn x))
(coll? x) (into (empty x) (map thisfn x))
(goog.isArray x) (vec (map thisfn x))
(identical? (type x) js/Object) (into {} (for [k (js-keys x)]
[(keyfn k)
(thisfn (aget x k))]))
:else x))]
(f x)))
([x] (-js->clj x {:keywordize-keys false}))))

(defn js->clj
"Recursively transforms JavaScript arrays into ClojureScript
vectors, and JavaScript objects into ClojureScript maps. With
option ':keywordize-keys true' will convert object fields from
strings to keywords."
[x & options]
(let [{:keys [keywordize-keys]} options
keyfn (if keywordize-keys keyword str)
f (fn thisfn [x]
(cond
(seq? x) (doall (map thisfn x))
(coll? x) (into (empty x) (map thisfn x))
(goog.isArray x) (vec (map thisfn x))
(identical? (type x) js/Object) (into {} (for [k (js-keys x)]
[(keyfn k)
(thisfn (aget x k))]))
:else x))]
(f x)))
[x & opts]
(-js->clj x (apply array-map opts)))

(defn memoize
"Returns a memoized version of a referentially transparent function. The
@@ -7209,4 +7255,3 @@ reduces them without incurring seq initialization"
IHash
(-hash [this]
(goog.string/hashCode (pr-str this))))

@@ -682,6 +682,24 @@
(assert (= [[{:a 1, :b 2} {:a 1, :b 2}]]
(js->clj [[{:a 1, :b 2} {:a 1, :b 2}]])))

;; clj->js
(assert (= (clj->js 'a) "a"))
(assert (= (clj->js :a) "a"))
(assert (= (clj->js "a") "a"))
(assert (= (clj->js 1) 1))
(assert (= (clj->js nil) (js* "null")))
(assert (= (clj->js true) (js* "true")))
(assert (goog/isArray (clj->js [])))
(assert (goog/isArray (clj->js #{})))
(assert (goog/isArray (clj->js '())))
(assert (goog/isObject (clj->js {})))
(assert (= (aget (clj->js {:a 1}) "a") 1))
(assert (= (-> (clj->js {:a {:b {{:k :ey} :d}}})
(aget "a")
(aget "b")
(aget "{:k :ey}"))
"d"))

;; last
(assert (= nil (last nil)))
(assert (= 3 (last [1 2 3])))
@@ -1516,7 +1534,7 @@
(b c d) :sym
:none)
:none)))

;; IComparable
(assert (= 0 (compare false false)))
(assert (= -1 (compare false true)))

4 comments on commit cd66e6b

@saolsen

This comment has been minimized.

Copy link

replied Nov 19, 2012

Yes! I've been including my own hacky version of this in every cljs project thus far.

@franks42

This comment has been minimized.

Copy link
Contributor

replied Nov 20, 2012

@bodil

This comment has been minimized.

Copy link

replied Nov 20, 2012

<3

@jocrau

This comment has been minimized.

Copy link

replied Nov 21, 2012

Thank you!

Please sign in to comment.
You can’t perform that action at this time.