我试图想出一种将特定转换函数应用于数据映射的好方法。
以示例地图:
为例{:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}}
并将其转换为:
{:name "foo bar"
:address "line1 /n line2"
:age 86}
我也希望转换能够反过来工作,尽管我不介意编写单独的转换。
到目前为止,我已经尝试编写转换函数列表:(伪)
(-> start-map
transform-name
transform-address
transform-age)
每个变换采用[start-map {accumulator-map}]。我还尝试编写一个包含变换后的地图的键的地图,并将变换函数(和参数)作为它们的值。我觉得我错过了一个技巧。
答案 0 :(得分:1)
你有正确的基本想法。我将如何做到这一点:
(ns tst.clj.core
(:use clj.core
clojure.test))
(def data
{:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}
)
(def target
{:name "foo bar"
:address "line1\nline2"
; :age 86 ; left as an excercise to the reader :)
})
(defn transform-name [m]
{:name (str (:firstName m) " "
(:lastName m))})
(defn transform-addr [m]
{:address (str (:addressLine1 m) \newline
(:addressLine2 m))})
(defn transform-person-simple [m]
(merge (transform-name m)
(transform-addr m)))
; You could also use the obscure function `juxt`, although this is
; more likely to confuse people.
; See http://clojuredocs.org/clojure.core/juxt
(defn transform-person-juxt [m]
(let [tx-juxt (juxt transform-name transform-addr)
juxt-answers (tx-juxt m)
result (into {} juxt-answers) ]
result ))
(deftest t-tx
(is (= target (transform-person-simple data)))
(is (= target (transform-person-juxt data)))
)
结果:
> lein test
(:repositories detected in user-level profiles! [:user]
See https://github.com/technomancy/leiningen/wiki/Repeatability)
lein test tst.clj.core
Ran 1 tests containing 2 assertions.
0 failures, 0 errors.
答案 1 :(得分:1)
变换:
(require '[clj-time.core :as t])
(require '[clj-time.format :as f])
(def data {:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}})
(def transformed
{:name (str (get-in data [:wrapper :firstName])
" "
(get-in data [:wrapper :lastName]))
:address (str (get-in data [:wrapper :addressLine1])
"\n"
(get-in data [:wrapper :addressLine2]))
:age (t/in-years (t/interval (f/parse
(get-in data [:wrapper :birthDate :iso] data))
(t/now)))})
逆变换。请注意,日期丢失精度。
(require '[clojure.string :as str])
(def untransformed
(let [[firstname lastname] (str/split
(:name transformed)
#" ")
[addressline1 addressline2] (str/split
(:address transformed)
#"\n")]
{:wrapper
{:firstName firstname
:lastName lastname
:addressLine1 addressline1
:addressLine2 addressline2
:birthDate
{:iso (f/unparse
(f/formatters :date)
(t/minus (t/now)
(t/years (:age transformed))))}}}))
答案 2 :(得分:1)
zipmap,juxt和destructuring对于地图转换非常方便。
(defn unwrap [{person :wrapper}]
(let [date-format (java.text.SimpleDateFormat. "yyyy-MM-dd")
name-fn #(str (:firstName %) " " (:lastName %))
address-fn #(str (:addressLine1 %) \newline (:addressLine1 %))
age-fn #(- (.getYear (java.util.Date.))
(.getYear (.parse date-format (get-in % [:birthDate :iso]))))]
(zipmap [:name :address :age]
((juxt name-fn address-fn age-fn) person))))
答案 3 :(得分:0)
您还可以使用您提供的键和转换功能将映射定义为数据结构。 E.g。
(def a->b
'[[:name (->fullname [:wrapper :firstName] [:wrapper :lastName])]
[:address [:wrapper :addressLine1]] ;; left as an exercise for the reader :)
[:age (->age [:wrapper :birthDate :iso])]])
,其中
(defn ->fullname [& s] (str/join " " s))
(defn ->age [s]
(let [now (Date.)
d (Date. s)]
(- (.getYear now) (.getYear d))))
然后实现一个函数来使用映射规则和源映射进行转换:
(transform a->b {:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930/03/12"}}})
=>
{:name "foo bar", :address "line1", :age 86}
快速实施可以是这样的:
(defn get-val [src s]
(if-let [v (or (get src s)
(get-in src s))]
v
(let [[f & ss] s
mf (resolve f)]
(apply mf (map (partial get-val src) ss)))))
(defn transform [m src]
(reduce (fn [ans [t s]]
(let [af (if (coll? t) assoc-in assoc)]
(af ans t (get-val src s))))
(empty src)
m))
答案 4 :(得分:0)
为了使它成为通用的,我将创建转换函数,从源对象中选择路径,将选定的值处理为目标路径到值的映射:
(defn transform [source target paths transformation]
(reduce (partial apply assoc-in)
target
(apply transformation
(map #(get-in source %) paths))))
然后你可以像这样使用它:
user> (def data {:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}})
#'user/data
user> (def data-2
(let [tr (partial transform data)]
(-> {}
(tr [[:wrapper :firstName] [:wrapper :lastName]]
(fn [f l] {[:name] (str f \space l)}))
(tr [[:wrapper :addressLine1] [:wrapper :addressLine2]]
(fn [a1 a2] {[:address] (str a1 \newline a2)}))
(tr [[:wrapper :birthDate :iso]]
(fn [d] {[:age] (reverse d)})))))
#'user/data-2
;;{:name "foo bar",
;; :address "line1\nline2",
;; :age (\2 \1 \- \3 \0 \- \0 \3 \9 \1)}
反之亦然:
user> (let [tr (partial transform data-2)]
(-> {}
(tr [[:name]]
(fn [n]
(let [[n1 n2] (clojure.string/split n #"\s")]
{[:wrapper :firstName] n1
[:wrapper :lastName] n2})))
(tr [[:address]]
(fn [a]
(let [[a1 a2] (clojure.string/split a #"\n")]
{[:wrapper :addressLine1] a1
[:wrapper :addressLine2] a2})))
(tr [[:age]]
(fn [a] {[:wrapper :birthDate :iso]
(apply str (reverse a))}))))
;;{:wrapper {:firstName "foo",
;; :lastName "bar",
;; :addressLine1 "line1",
;; :addressLine2 "line2",
;; :birthDate {:iso "1930-03-12"}}}
答案 5 :(得分:0)
使用perc
,您可以像这样轻松完成此操作:
(#%/%{:name (str %:firstName " " %:lastName)
:address (str %:addressLine1 " \n " %:addressLine2)
:age (-> %:birthDate :iso ->age)}
(:wrapper original-map))
->age
来自rmcv的示例。
很简单,对吧?而且,我们甚至不必为这些事情起一堆新的本地名称!