ClojureScript - convert arbitrary JavaScript object to Clojure Script map

蓝咒 提交于 2019-12-03 12:19:56

js->clj only works for Object, anything with custom constructor (see type) will be returned as is.

see: https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/core.cljs#L9319

I suggest doing this instead:

(defn jsx->clj
  [x]
  (into {} (for [k (.keys js/Object x)] [k (aget x k)])))

UPDATE for correct solution see Aaron's answer, gotta use goog.object

The accepted answer wasn't working for me with the javascript object window.performance.timing. This is because Object.keys() doesn't actually return the props for the PerformanceTiming object.

(.keys js/Object (.-timing (.-performance js/window))
; => #js[]

This is despite the fact that the props of PerformanceTiming are indeed iterable with a vanilla JavaScript loop:

for (a in window.performance.timing) {
  console.log(a);
}
// navigationStart
// unloadEventStart
// unloadEventEnd
// ...

The following is what I came up with to convert an arbitrary JavaScript object to a ClojureScript map. Note the use of two simple Google Closure functions.

  • goog.typeOf wraps typeof, which isn't normally accessible to us in ClojureScript. I use this to filter out props which are functions.
  • goog.object.getKeys wraps for (prop in obj) {...}, building up an array result which we can reduce into a map.

Solution (flat)

(defn obj->clj
  [obj]
  (-> (fn [result key]
        (let [v (goog.object/get obj key)]
          (if (= "function" (goog/typeOf v))
            result
            (assoc result key v))))
      (reduce {} (.getKeys goog/object obj))))

Solution (recursive)

Update: This solution will work for nested maps.

(defn obj->clj
  [obj]
  (if (goog.isObject obj)
    (-> (fn [result key]
          (let [v (goog.object/get obj key)]
            (if (= "function" (goog/typeOf v))
              result
              (assoc result key (obj->clj v)))))
        (reduce {} (.getKeys goog/object obj)))
    obj))

Two approaches that do not require writing custom conversion functions - they both employ standard JavaScript functions to loose the custom prototype and thus enable clj->js to work correctly.

Using JSON serialization

This approach just serializes to JSON and immediately parses it:

(js->clj (-> e js/JSON.stringify js/JSON.parse))

Advantages:

  • does not require any helper function
  • works for nested objects, with/without prototype
  • supported in every browser

Disadvantages:

  • performance might be a problem in critical pieces of codebase
  • will strip any non-serializable values, like functions.

Using Object.assign()

This approach is based on Object.assign() and it works by copying all the properties from e onto a fresh, plain (no custom prototype) #js {}.

(js->clj (js/Object.assign #js {} e))

Advantages:

  • does not require any helper function

Disadvantages:

  • works on flat objects, if there is another nested object withing e, it won't be converted by clj->js.
  • Object.assign() is not supported by old browsers, most notably - IE.
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!