Search code examples
clojuretransformation

Clojure: Transform nested maps into custom map keeping only specific attributes


I have a vector of maps (result of xml/parse) which contains the following vector of nested maps (I already got rid of some parts I don't want to keep):

[
{: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"]}
    ]}
]}
]

Now I want to extract only some specific data from this structure, producing a result which looks like this:

[
{"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"}
]

Which clojure tool could help me accomplish this?

I've found another question which is a bit similar, but whenever I tried some version of a map call the result I got was some kind of clojure.lang.LazySeq or clojure.core$map which I couldn't get to print properly to verify the result.


Solution

  • usually you can start from the bottom, gradually going up:

    first you would like to parse the attr item:

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

    then you would turn every item into a map of 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"}
    

    so the last thing you need do, is to map over the top level form, producing the required result:

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