【问题标题】:Clojure: Transform nested maps into custom map keeping only specific attributesClojure:将嵌套地图转换为仅保留特定属性的自定义地图
【发布时间】:2018-10-08 15:49:32
【问题描述】:

我有一个地图向量(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,我不能正确打印以验证结果。

【问题讨论】:

    标签: clojure transformation


    【解决方案1】:

    通常你可以从底部开始,逐渐向上:

    首先您要解析 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"]
    

    然后你会将每个项目变成一个属性图:

    (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"}]
    

    【讨论】:

    • 那行得通,更好的是我能够理解它,感谢您的出色回答。
    【解决方案2】:

    您可以使用 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

    【讨论】:

      【解决方案3】:

      您还可以编写传感器。前几天我在 JUXT 博客上阅读了有关使用传感器创建类似 xpath 的功能的内容。

      (def children (map :content))
      
      (defn tagp [pred]
        (filter (comp pred :tag)))
      
      (defn tag= [tag-name]
        (tagp (partial = tag-name)))
      
      (def text (comp (mapcat :content) (filter string?)))
      
      (defn fields [obj-datas]
        (sequence (comp
                   (tag= :ObjectData)
                   (mapcat :content)
                   text)
                  obj-datas))
      
      (defn clean [xml-map]
        (let [fields-list (sequence (comp
                                     (tag= :SoapObject)
                                     children
                                     (map fields))
                                    xml-map)]
          (map (partial apply hash-map) fields-list)))
      

      【讨论】:

        【解决方案4】:

        这里不需要花哨的工具。您可以使用最简单的代码块。

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

        【讨论】:

          猜你喜欢
          • 2016-11-14
          • 1970-01-01
          • 2014-04-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多