ClojureScript - convert arbitrary JavaScript object to Clojure Script map

前端 未结 4 1480
野趣味
野趣味 2021-02-13 20:10

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


        
相关标签:
4条回答
  • 2021-02-13 20:34
    (defn obj->clj
      ([obj]
       (obj->clj obj :keywordize-keys false))
      ([obj & opts]
       (let [{:keys [keywordize-keys]} opts
             keyfn (if keywordize-keys keyword str)]
         (if (and (not-any? #(% obj) [inst? uuid?])
                  (goog.isObject obj))
           (-> (fn [result k]
                 (let [v (goog.object/get obj k)]
                   (if (= "function" (goog/typeOf v))
                     result
                     (assoc result (keyfn k) (apply obj->clj v opts)))))
               (reduce {} (.getKeys goog/object obj)))
           obj))))
    

    Small problem with the original above is that JS treats #inst and #uuid as objects. Seems like those are the only tagged literals in clojure

    I also added the option to keywordize keys by looking at js->clj source

    0 讨论(0)
  • 2021-02-13 20:44

    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.
    0 讨论(0)
  • 2021-02-13 20:47

    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

    0 讨论(0)
  • 2021-02-13 20:54

    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))
    
    0 讨论(0)
提交回复
热议问题