我在Clojure中有一个非常大的屁股嵌套地图,我正在寻找最惯用的方法来踢出钥匙,而钥匙不应该提供给前端(是的,该Clojure服务在后端运行)。
数据结构如下:
(def data
{:a 1
:b 2
:c 3
:d [{:e 5}
{:f 6
:g {
:h 8
:i 9
:j 10}
:l [{
:m 11
:n 12
:p {:q 13
:r 14
:s 15
}}
{:m 16
:n 17
:p {:q 18
:r 19
:s 20
}}]}]})
如您所见,我得到了一个带有键的地图,其中一些键得到了带有地图的列表,又有了一些列表...所以我知道->不好看。
但是...是否有某种方式描述我想要获取的数据,以便我不需要的每个键都被过滤掉?
Thx
答案 0 :(得分:7)
另一种方法,不使用任何外部库,而使用clojure.walk
:
(defn remove-deep [key-set data]
(clojure.walk/prewalk (fn [node] (if (map? node)
(apply dissoc node key-set)
node))
data))
user> (remove-deep [:i :l] data)
;;=> {:a 1, :b 2, :c 3, :d [{:e 5} {:f 6, :g {:h 8, :j 10}}]}
user> (remove-deep [:f :p] data)
;;=> {:a 1, :b 2, :c 3, :d [{:e 5} {:g {:h 8, :i 9, :j 10}, :l [{:m 11, :n 12} {:m 16, :n 17}]}]}
针对您的确切用例,可以进行pre / postwalk:沿着异构集合走去,必要时转换值
答案 1 :(得分:4)
如果只想过滤顶级密钥,则可以使用select-keys
如果要删除深度嵌套的键,可以使用specter。例如,要删除:h
下向量中每个项目下:g
下:d
下所有值,只需输入:
user> (setval [:d ALL :g :h] NONE data)
答案 2 :(得分:4)
最简单的是to use clojure.walk/postwalk
。我假设您不必担心诸如“删除:i仅在它是:f的子级时”之类的键组合。
这里是一个例子:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require [clojure.walk :as walk]))
(def data
{:a 1
:b 2
:c 3
:d [{:e 5}
{:f 6
:g {
:h 8
:i 9
:j 10}
:l [{
:m 11
:n 12
:p {:q 13
:r 14
:s 15
}}
{:m 16
:n 17
:p {:q 18
:r 19
:s 20
}}]}]})
(defn remove-keys [data keys]
(let [proc-node (fn [node]
(spyx node))
result (walk/postwalk proc-node data) ]
(spyx-pretty result)))
(def bad-keys #{:b :f :i :p :n})
(dotest
(remove-keys data bad-keys))
这显示了postwalk
的递归处理,输出为:
Testing tst.demo.core
node => :a
node => 1
node => [:a 1]
node => :b
node => 2
node => [:b 2]
node => :c
node => 3
node => [:c 3]
node => :d
node => :e
node => 5
node => [:e 5]
node => {:e 5}
node => :f
node => 6
node => [:f 6]
node => :g
node => :h
node => 8
node => [:h 8]
node => :i
node => 9
node => [:i 9]
node => :j
node => 10
node => [:j 10]
node => {:h 8, :i 9, :j 10}
node => [:g {:h 8, :i 9, :j 10}]
node => :l
node => :m
node => 11
node => [:m 11]
node => :n
node => 12
node => [:n 12]
node => :p
node => :q
node => 13
node => [:q 13]
node => :r
node => 14
node => [:r 14]
node => :s
node => 15
node => [:s 15]
node => {:q 13, :r 14, :s 15}
node => [:p {:q 13, :r 14, :s 15}]
node => {:m 11, :n 12, :p {:q 13, :r 14, :s 15}}
node => :m
node => 16
node => [:m 16]
node => :n
node => 17
node => [:n 17]
node => :p
node => :q
node => 18
node => [:q 18]
node => :r
node => 19
node => [:r 19]
node => :s
node => 20
node => [:s 20]
node => {:q 18, :r 19, :s 20}
node => [:p {:q 18, :r 19, :s 20}]
node => {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}
node => [{:m 11, :n 12, :p {:q 13, :r 14, :s 15}} {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]
node => [:l [{:m 11, :n 12, :p {:q 13, :r 14, :s 15}} {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]]
node => {:f 6, :g {:h 8, :i 9, :j 10}, :l [{:m 11, :n 12, :p {:q 13, :r 14, :s 15}} {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]}
node => [{:e 5} {:f 6, :g {:h 8, :i 9, :j 10}, :l [{:m 11, :n 12, :p {:q 13, :r 14, :s 15}} {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]}]
node => [:d [{:e 5} {:f 6, :g {:h 8, :i 9, :j 10}, :l [{:m 11, :n 12, :p {:q 13, :r 14, :s 15}} {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]}]]
node => {:a 1, :b 2, :c 3, :d [{:e 5} {:f 6, :g {:h 8, :i 9, :j 10}, :l [{:m 11, :n 12, :p {:q 13, :r 14, :s 15}} {:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]}]}
result =>
{:a 1,
:b 2,
:c 3,
:d
[{:e 5}
{:f 6,
:g {:h 8, :i 9, :j 10},
:l
[{:m 11, :n 12, :p {:q 13, :r 14, :s 15}}
{:m 16, :n 17, :p {:q 18, :r 19, :s 20}}]}]}
您可以看到,映射首先被转换为键值对的向量,例如[:n 17]
。因此,当您得到这样的2-vec时,只需看一下第一项并返回nil
(如果您不喜欢它的话):
(defn len-2-vec? [node]
(and (sequential? node)
(= 2 (count node))))
(defn remove-keys [data bad-keys]
(let [proc-node (fn [node]
(if (and (len-2-vec? node)
(contains? bad-keys (first node)))
(do
(spyx :removed node)
nil)
node))
result (walk/postwalk proc-node data) ]
(spyx-pretty result)))
(def bad-keys #{:b :f :i :p :n})
(dotest
(remove-keys data bad-keys))
并输出:
Testing tst.demo.core
:removed node => [:b 2]
:removed node => [:f 6]
:removed node => [:i 9]
:removed node => [:n 12]
:removed node => [:p {:q 13, :r 14, :s 15}]
:removed node => [:n 17]
:removed node => [:p {:q 18, :r 19, :s 20}]
(remove-keys data bad-keys) =>
{:a 1,
:c 3,
:d [{:e 5}
{:g {:h 8,
:j 10},
:l [{:m 11}
{:m 16}]}]}
Ran 2 tests containing 0 assertions.
0 failures, 0 errors.
Here is the doc for spyx
。
答案 3 :(得分:3)
这可能使用了比所需更多的“手动提升”功能,但是一个简单的递归函数可以很好地解决这一问题:
(defn filter-nested [root keys-to-remove]
(let [should-remove? (set keys-to-remove)
; A recursive function to search through the map
f (fn rec [node]
(reduce-kv (fn [acc k v]
(cond
; If it's in the set, remove the key from the node
(should-remove? k) (dissoc acc k)
; If the value is a map, recursively search it too
(map? v) (assoc acc k (rec v))
; If it's a vector, map a recursive call over the vector
(vector? v) (assoc acc k (mapv rec v))
; Else do nothing
:else acc))
node
node))]
(f root)))
(filter-nested data #{:l})
=> {:a 1, :b 2, :c 3, :d [{:e 5} {:f 6, :g {:h 8, :i 9, :j 10}}]}
一旦您考虑了解释性注释,它并没有看起来那么大。 f
(内部命名为rec
)是一种递归函数,当dissoc
在找到的映射中的键位于提供的键列表中时,这些键便会递归。当它找到的值是地图或向量时,也会递归搜索它们。
答案 4 :(得分:1)
我们希望使用某种白名单,而不是使用黑名单。在生产环境中,使用黑名单并不是一个好主意-如果出于某种原因可能扩展了响应对象。因此,我们现在将https://github.com/metosin/spec-tools与strip-extra-keys-transformer
一起使用,例如:
(ns sexy.helper.transformer
(:require [spec-tools.core :as st]
[spec-tools.data-spec :as ds]))
(def my-abc {:a "12345"
:b "00529"
:c [{:d "Kartoffel"
:e 5}
{:d "Second Item"
:e 9999}]})
(def the-abc
{:a string?
:c [{:d string?}]})
(def abc-spec
(ds/spec ::abc the-abc))
(st/conform abc-spec my-abc st/strip-extra-keys-transformer)