ClojureScript - 将任意JavaScript对象转换为Clojure Script映射

时间:2015-09-08 21:10:58

标签: clojure clojurescript

我正在尝试将Javascript对象转换为Clojure。但是,我收到以下错误:

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

是的,此对象来自Geolocation API。我想我必须延长IEncodeClojureIWriter,但我不知道如何。

例如添加以下内容:

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

加载我的代码时出错:Uncaught TypeError: Cannot read property 'prototype' of undefined

4 个答案:

答案 0 :(得分:9)

js->clj仅适用于Object,任何带有自定义构造函数的内容(请参阅type)都将按原样返回。

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

我建议改为:

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

更新以获得正确的解决方案请参阅Aaron的回答,必须使用goog.object

答案 1 :(得分:7)

使用javascript对象window.performance.timing,我接受的答案对我不起作用。这是因为Object.keys()实际上并没有返回PerformanceTiming对象的道具。

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

尽管事实上PerformanceTiming的道具确实可以通过一个普通的JavaScript循环进行迭代:

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

以下是我提出的将任意JavaScript对象转换为ClojureScript映射的方法。请注意使用两个简单的Google Closure功能。

  • goog.typeOf包裹typeof,这在我们ClojureScript中通常无法访问。我用这个过滤掉了作为功能的道具。
  • goog.object.getKeys包裹for (prop in obj) {...},构建一个我们可以缩减为地图的数组结果。

解决方案(持平)

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

解决方案(递归)

更新:此解决方案适用于嵌套地图。

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

答案 2 :(得分:1)

两种不需要编写自定义转换函数的方法-它们都采用标准的JavaScript函数来释放自定义原型,从而使clj->js能够正常工作。

使用JSON序列化

这种方法只是序列化为JSON并立即对其进行解析:

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

优势:

  • 不需要任何辅助功能
  • 适用于带有/不带有原型的嵌套对象
  • 每个浏览器都支持

缺点:

  • 性能可能是关键代码库中的问题
  • 将剥离所有不可序列化的值,例如函数。

使用Object.assign()

此方法基于Object.assign(),它的工作原理是将e中的所有属性复制到新的普通(无自定义原型)#js {}中。

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

优势:

  • 不需要任何辅助功能

缺点:

  • 适用于平面对象,如果还有另一个嵌套对象带有e,则clj->js不会转换它。
  • Object.assign() is not supported by old browsers,最值得注意的是IE。

答案 3 :(得分:0)

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

以上原始内容的小问题是JS将#inst和#uuid视为对象。似乎这些是Clojure中唯一带标签的文字

我还通过查看js-> clj源代码添加了将关键字关键字化的选项