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。我想我必须扩展 IEncodeClojure
和 IWriter
,但我不知道如何扩展。
例如添加以下内容:
(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 源来对键进行关键字化的选项
我正在尝试将 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。我想我必须扩展 IEncodeClojure
和 IWriter
,但我不知道如何扩展。
例如添加以下内容:
(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 源来对键进行关键字化的选项