Clojure:将嵌套地图转换为仅保留特定属性的自定义地图

时间:2018-10-08 15:49:33

标签: clojure transformation

我有一个地图矢量(xml / parse的结果),其中包含以下嵌套地图矢量(我已经摆脱了一些我不想保留的部分):

[
{:tag :SoapObject, :attrs nil, :content [
    {:tag :ObjectData, :attrs nil, :content [
        {:tag :FieldName, :attrs nil, :content ["ID"]}
        {:tag :FieldValue, :attrs nil, :content ["8d8edbb6-cb0f-11e8-a8d5-f2801f1b9fd1"]}
    ]}
    {:tag :ObjectData, :attrs nil, :content [
        {:tag :FieldName, :attrs nil, :content ["Attribute_1"]}
        {:tag :FieldValue, :attrs nil, :content ["Value_1a"]}
    ]} 
    {:tag :ObjectData, :attrs nil, :content [
        {:tag :FieldName, :attrs nil, :content ["Attribute_2"]}
        {:tag :FieldValue, :attrs nil, :content ["Value_2a"]}
    ]} 
]}
{:tag :SoapObject, :attrs nil, :content [
    {:tag :ObjectData, :attrs nil, :content [
        {:tag :FieldName, :attrs nil, :content ["ID"]}
        {:tag :FieldValue, :attrs nil, :content ["90e39036-cb0f-11e8-a8d5-f2801f1b9fd1"]}
    ]}
    {:tag :ObjectData, :attrs nil, :content [
        {:tag :FieldName, :attrs nil, :content ["Attribute_1"]}
        {:tag :FieldValue, :attrs nil, :content ["Value_1b"]}
    ]}
    {:tag :ObjectData, :attrs nil, :content [
        {:tag :FieldName, :attrs nil, :content ["Attribute_2"]}
        {:tag :FieldValue, :attrs nil, :content ["Value_2b"]}
    ]}
]}
]

现在,我只想从此结构中提取一些特定数据,以产生如下所示的结果:

[
{"ID" "8d8edbb6-cb0f-11e8-a8d5-f2801f1b9fd1",
"Attribute_1" "Value_1a",
"Attribute_2" "Value_1a"}

{"ID" "90e39036-cb0f-11e8-a8d5-f2801f1b9fd1",
"Attribute_1" "Value_1b",
"Attribute_2" "Value_1b"}
]

哪种clojure工具可以帮助我完成此任务?

我发现another question有点类似,但是每当我尝试某个版本的map调用时,我得到的结果都是某种clojure.lang.LazySeq或clojure.core $ map,我无法无法正确打印以验证结果。

5 个答案:

答案 0 :(得分:3)

通常您可以从底部开始,然后逐渐上升:

首先,您想解析attr项:

(def first-content (comp first :content))

(defn get-attr [{[k v] :content}]
  [(first-content k)
   (first-content v)])

user> (get-attr {:tag :ObjectData, :attrs nil, :content [
        {:tag :FieldName, :attrs nil, :content ["ID"]}
        {:tag :FieldValue, :attrs nil, :content ["90e39036-cb0f-11e8-a8d5-f2801f1b9fd1"]}
        ]})
;;=> ["ID" "90e39036-cb0f-11e8-a8d5-f2801f1b9fd1"]

然后,您将把每一项变成一张attrs地图:

(defn parse-item [item]
  (into {} (map get-attr (:content item))))

(parse-item {:tag :SoapObject, :attrs nil, :content [
    {:tag :ObjectData, :attrs nil, :content [
        {:tag :FieldName, :attrs nil, :content ["ID"]}
        {:tag :FieldValue, :attrs nil, :content ["90e39036-cb0f-11e8-a8d5-f2801f1b9fd1"]}
    ]}
    {:tag :ObjectData, :attrs nil, :content [
        {:tag :FieldName, :attrs nil, :content ["Attribute_1"]}
        {:tag :FieldValue, :attrs nil, :content ["Value_1b"]}
    ]}
    {:tag :ObjectData, :attrs nil, :content [
        {:tag :FieldName, :attrs nil, :content ["Attribute_2"]}
        {:tag :FieldValue, :attrs nil, :content ["Value_2b"]}
    ]}
]})

;;=> {"ID" "90e39036-cb0f-11e8-a8d5-f2801f1b9fd1", "Attribute_1" "Value_1b", "Attribute_2" "Value_2b"}

因此,您需要做的最后一件事是映射到顶层表单,生成所需的结果:

(mapv parse-item data)

;;=> [{"ID" "8d8edbb6-cb0f-11e8-a8d5-f2801f1b9fd1", "Attribute_1" "Value_1a", "Attribute_2" "Value_2a"} 
;;    {"ID" "90e39036-cb0f-11e8-a8d5-f2801f1b9fd1", "Attribute_1" "Value_1b", "Attribute_2" "Value_2b"}]

答案 1 :(得分:0)

您可以使用Tupelo Forest库轻松解决基于树的问题。您可以看到视频介绍from last year's Clojure Conj here

对于您的问题,我将按以下方式处理。首先,数据:

(dotest
  (let [data-enlive
        {:tag   :root
         :attrs nil
         :content
            [{:tag     :SoapObject, :attrs nil,
              :content 
                 [{:tag     :ObjectData, :attrs nil,
                   :content [{:tag :FieldName, :attrs nil, :content ["ID"]}
                             {:tag :FieldValue, :attrs nil, :content ["8d8edbb6-cb0f-11e8-a8d5-f2801f1b9fd1"]}]}
                  {:tag     :ObjectData, :attrs nil,
                   :content [{:tag :FieldName, :attrs nil, :content ["Attribute_1"]}
                             {:tag :FieldValue, :attrs nil, :content ["Value_1a"]}]}
                  {:tag     :ObjectData, :attrs nil,
                   :content [{:tag :FieldName, :attrs nil, :content ["Attribute_2"]}
                             {:tag :FieldValue, :attrs nil, :content ["Value_2a"]}]}]}
             {:tag     :SoapObject, :attrs nil,
              :content
                 [{:tag     :ObjectData, :attrs nil,
                   :content [{:tag :FieldName, :attrs nil, :content ["ID"]}
                             {:tag :FieldValue, :attrs nil, :content ["90e39036-cb0f-11e8-a8d5-f2801f1b9fd1"]}]}
                  {:tag     :ObjectData, :attrs nil,
                   :content [{:tag :FieldName, :attrs nil, :content ["Attribute_1"]}
                             {:tag :FieldValue, :attrs nil, :content ["Value_1b"]}]}
                  {:tag     :ObjectData, :attrs nil,
                   :content [{:tag :FieldName, :attrs nil, :content ["Attribute_2"]}
                             {:tag :FieldValue, :attrs nil, :content ["Value_2b"]}]}]}]}]

然后是代码

(with-debug-hid
  (with-forest (new-forest)
    (let [root-hid     (add-tree-enlive data-enlive)
          soapobj-hids (find-hids root-hid [:root :SoapObject])
          objdata->map (fn [objdata-hid]
                         (let [fieldname-node  (hid->node (find-hid objdata-hid [:ObjectData :FieldName]))
                               fieldvalue-node (hid->node (find-hid objdata-hid [:ObjectData :FieldValue]))]
                           { (grab :value fieldname-node) (grab :value fieldvalue-node) }))
          soapobj->map (fn [soapobj-hid]
                         (apply glue
                           (for [objdata-hid (hid->kids soapobj-hid)]
                             (objdata->map objdata-hid))))
          results      (mapv soapobj->map soapobj-hids)]

有中间结果:

          (is= (hid->bush root-hid)
            [{:tag :root}
             [{:tag :SoapObject}
              [{:tag :ObjectData}
               [{:tag :FieldName, :value "ID"}]
               [{:tag :FieldValue, :value "8d8edbb6-cb0f-11e8-a8d5-f2801f1b9fd1"}]]
              [{:tag :ObjectData}
               [{:tag :FieldName, :value "Attribute_1"}]
               [{:tag :FieldValue, :value "Value_1a"}]]
              [{:tag :ObjectData}
               [{:tag :FieldName, :value "Attribute_2"}]
               [{:tag :FieldValue, :value "Value_2a"}]]]
             [{:tag :SoapObject}
              [{:tag :ObjectData}
               [{:tag :FieldName, :value "ID"}]
               [{:tag :FieldValue, :value "90e39036-cb0f-11e8-a8d5-f2801f1b9fd1"}]]
              [{:tag :ObjectData}
               [{:tag :FieldName, :value "Attribute_1"}]
               [{:tag :FieldValue, :value "Value_1b"}]]
              [{:tag :ObjectData}
               [{:tag :FieldName, :value "Attribute_2"}]
               [{:tag :FieldValue, :value "Value_2b"}]]]])
          (is= soapobj-hids [:0009 :0013])

和最终结果:

          (is= results
            [{"ID"          "8d8edbb6-cb0f-11e8-a8d5-f2801f1b9fd1",
              "Attribute_1" "Value_1a",
              "Attribute_2" "Value_2a"}
             {"ID"          "90e39036-cb0f-11e8-a8d5-f2801f1b9fd1",
              "Attribute_1" "Value_1b",
              "Attribute_2" "Value_2b"}]))))))

更多文档仍在进行中,但是您可以see API docs herelive example of your problem here

答案 2 :(得分:0)

您也可以组成换能器。前几天,我在JUXT博客上阅读了一些有关使用换能器创建xpath之类功能的信息。

webpack: {
  module: {
    rules: [
      { test: /\.js/, exclude: /node_modules/, loader: 'babel-loader' }
    ]
  },
  watch: true,
  mode: 'none'
},

答案 3 :(得分:0)

这里不需要精美的工具。您可以摆脱最简单的代码块。

(use '[plumbing.core])
(let [A ...your-data...]
    (map (fn->> :content
            (mapcat :content)
            (mapcat :content)
            (apply hash-map)) 
         A))

答案 4 :(得分:0)

使用perc,您可以执行以下操作:

(->> original-data
  (mapv
    #%/%(->> %:content
          (map
            #%/$(->> $:content
                  (apply
                    #%/?{(first ?1:content)
                         (first ?2:content)})))
          (apply merge))))

它看起来似乎有些惯用,但方式有所不同。