我会从下往上开始,为:tags 条目创建转换函数,然后为:metrics 和:sources。
假设我们的转换函数仅通过计算标签来生成 id(只是为了说明,以后可以轻松更改):
(defn transform [tags] (count tags))
user> (transform ["asd" "dsf"])
;;=> 2
然后对度量条目应用转换:
(defn transform-metric [{:keys [tags] :as m}]
(assoc m :id (transform tags)))
user> (transform-metric {:tags ["a noether tag" "aegn"]})
;;=> {:tags ["a noether tag" "aegn"], :id 2}
现在使用transform-metric 更新源条目:
(defn transform-source [s]
(update s :metrics #(mapv transform-metric %)))
user> (transform-source {:tags ["s:my:tags"],
:metrics [{:tags ["a tag"]}
{:tags ["a noether tag" "aegn"]}
{:tags ["eare" "rh"]}]})
;;=> {:tags ["s:my:tags"],
;; :metrics [{:tags ["a tag"], :id 1}
;; {:tags ["a noether tag" "aegn"], :id 2}
;; {:tags ["eare" "rh"], :id 2}]}
最后一步是转换整个数据:
(defn transform-data [d]
(update d :sources #(mapv transform-source %)))
user> (transform-data data)
;;=> {:tags ["type:something" "gw:somethingelse"],
;; :sources [{:tags ["s:my:tags"],
;; :metrics [{:tags ["a tag"], :id 1}
;; {:tags ["a noether tag" "aegn"], :id 2}
;; {:tags ["eare" "rh"], :id 2}]}]}
所以,我们到此结束。
现在注意到transform-data 和transform-source 几乎相同,所以我们可以创建一个实用函数来生成这样的更新函数:
(defn make-vec-updater [field transformer]
(fn [data] (update data field (partial mapv transformer))))
使用这个函数,我们可以像这样定义深度转换:
(def transformer
(make-vec-updater
:sources
(make-vec-updater
:metrics
(fn [{:keys [tags] :as m}]
(assoc m :id (transform tags))))))
user> (transformer data)
;;=> {:tags ["type:something" "gw:somethingelse"],
;; :sources [{:tags ["s:my:tags"],
;; :metrics [{:tags ["a tag"], :id 1}
;; {:tags ["a noether tag" "aegn"], :id 2}
;; {:tags ["eare" "rh"], :id 2}]}]}
并且基于这种转换器构造方法,我们可以创建一个很好的函数来更新vectors-of-maps-of-vectors-of-maps-of-vectors...结构中的值,具有任意嵌套级别:
(defn update-in-v [data ks f]
((reduce #(make-vec-updater %2 %1) f (reverse ks)) data))
user> (update-in-v data [:sources :metrics]
(fn [{:keys [tags] :as m}]
(assoc m :id (transform tags))))
;;=> {:tags ["type:something" "gw:somethingelse"],
;; :sources [{:tags ["s:my:tags"],
;; :metrics [{:tags ["a tag"], :id 1}
;; {:tags ["a noether tag" "aegn"], :id 2}
;; {:tags ["eare" "rh"], :id 2}]}]}
更新
除了这种手动方法之外,还有一个名为 specter 的出色库,它做的事情完全相同(还有更多),但显然更通用和可用:
(require '[com.rpl.specter :as sp])
(sp/transform [:sources sp/ALL :metrics sp/ALL]
(fn [{:keys [tags] :as m}]
(assoc m :id (transform tags)))
data)
;;=> {:tags ["type:something" "gw:somethingelse"],
;; :sources [{:tags ["s:my:tags"],
;; :metrics [{:tags ["a tag"], :id 1}
;; {:tags ["a noether tag" "aegn"], :id 2}
;; {:tags ["eare" "rh"], :id 2}]}]}