/img/clojure-banner.png

Working with Nested Data Structures

Funtions like `assoc` and `dissoc` are great for single-level maps, but what about nested data?

Written by: Alex Root-Roatch | Sunday, October 13, 2024

Dealing with Nesting

Working with nested data can be tedious and challenging, especially if the goal is to add or update data deep inside a nested structure instead of simply retrieving the data. Luckily, Clojure provides us with get-in, assoc-in, and update-in to make this easier.

get-in

This Clojure function allows us to easily access data in a nested structure in a readable way. We simply provide a vector of the keys in a map or indices in a vector that we want to access. For example:

(def developers {:data [{:first-name "Alex"
                         :skills ["Clojure" "React"]}
                        {:first-name "Bob"
                         :skills ["SQL" "Datomic"]}]})
         
(get-in developers [:data 1 :skills 0])
=> "SQL"

assoc-in

This function allows us to add values to a map or vector that is nested rather than at the top level. For example:

(assoc-in developers [:data 0 :last-name] "Root")
=> {:data [{:first-name "Alex"
            :last-name "Root"
            :skills ["Clojure" "React"]}
           {:first-name "Bob"
            :skills ["SQL" "Datomic"]}]}
            
(assoc-in developers [:data 0 :skills 2] "SQL")
=> {:data [{:first-name "Alex"
            :skills ["Clojure" "React" "SQL"]}
           {:first-name "Bob"
            :skills ["SQL" "Datomic"]}]}

This works for inserting entire data structures as well:

(assoc-in developers [:data 2] {:first-name "Jane"
                                :skills ["Graphic Design" "Java"]})
=> {:data [{:first-name "Alex"
            :skills ["Clojure" "React"]}
           {:first-name "Bob"
            :skills ["SQL" "Datomic"]}
           {:first-name "Jane"
            :skills ["Graphic Design" "Java"]]}

Be careful when inserting into a vector though, as inserting at a pre-existing index will overwrite the data that is already there.

update-in

This function works like assoc-in but allows us to specify exactly what function is performed at the specified place in the data instead of just assoc. For example:

(update-in developers [:data 0 :skills 0] str "Script")
=> {:data [{:first-name "Alex"
            :skills ["ClojureScript" "React"]}
           {:first-name "Bob"
            :skills ["SQL" "Datomic"]}]}

The selected value, "Clojure," is passed as the first argument to the provided function, str, followed by a second argument "Script" to make "ClojureScript".

This is especially useful for removing items, since there is no dissoc-in function:

(update-in developers [:data 0] dissoc :skills)
=> {:data [{:first-name "Alex"}
           {:first-name "Bob"
            :skills ["SQL" "Datomic"]}]}

Conclusion

Dealing with nested data can be annoying, but with Clojure's get-in, assoc-in, and update-in functions, we can easily achieve what we need to with those deeply nested values.

Explore more articles

Browse All Posts