ClojureScript - convert arbitrary JavaScript object to Clojure Script map

爷,独闯天下 提交于 2019-12-21 03:56:08

问题


I am trying to convert a Javascript object to a Clojure. However, I get the following error :

 (js/console.log (js->clj e)) ;; has no effect
 (pprint (js->clj e)) ;; No protocol method IWriter.-write defined for type object: [object Geoposition]

Yes, this object comes from the Geolocation API. I suppose that I have to extend IEncodeClojure and IWriter, but I have no clue how.

For instance adding the following :

(extend-protocol IEncodeClojure
  Coordinates
  (-js->clj [x options]
    (println "HERE " x options)))

Yields an error when loading my code : Uncaught TypeError: Cannot read property 'prototype' of undefined


回答1:


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




回答2:


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))



回答3:


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.


来源:https://stackoverflow.com/questions/32467299/clojurescript-convert-arbitrary-javascript-object-to-clojure-script-map

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!