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

ClojureScript - convert arbitrary JavaScript object to Clojure Script map

我正在尝试将 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]

是的,这个对象来自地理定位API。我想我必须扩展 IEncodeClojureIWriter,但我不知道如何扩展。

例如添加以下内容:

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

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

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

UPDATE 正确的解决方案请参阅 Aaron 的回答,必须使用 goog.object

已接受的答案不适用于 javascript 对象 window.performance.timing。这是因为 Object.keys() 实际上并不 return 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 闭包函数的使用。

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

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

使用JSON序列化

这种方法只是序列化为 JSON 并立即解析它:

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

优点:

  • 不需要任何辅助函数
  • 适用于嵌套对象,with/without原型
  • 所有浏览器都支持

缺点:

  • 性能可能是代码库关键部分的问题
  • 将去除任何不可序列化的值,例如函数。

使用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。
(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 源来对键进行关键字化的选项