Ben Siver
Ben Siver

Reputation: 2948

Filtering a list of maps in clojure with potentially different keys

Say I have a list of maps that looks like the following:

(def my-map '({:some-key {:another-key "val"}
  :id "123"}
 {:some-key {:another-key "val"}
  :id "456"}
 {:some-other-key {:a-different-key "val2"}
  :id "789"})

In my attempt to filter this map by :another-key, I tried this:

(filter #(= "val" ((% :some-key) :another-key)) my-map)))

However, this will throw a NullPointerException on the map entry that doesn't contain the key I'm filtering on. What would be the optimal way to filter this map, excluding entries that don't match the filtered schema entirely?

Upvotes: 1

Views: 303

Answers (1)

ClojureMostly
ClojureMostly

Reputation: 4713

Your first lookup of the key :some-key will return nil if the map key is not in the map. Calling nil will result in the NPE you see.

The solution is easy, just make the keyword lookup itself in the map which work even if given a nil:

(def my-map '({:some-key {:another-key "val"}
               :id "123"}
               {:some-key {:another-key "val"}
                :id "456"}
               {:some-other-key {:a-different-key "val2"}
                :id "789"}))

(filter #(= "val" (:another-key (% :some-key))) my-map)

You can also use get-in:

(filter #(= "val" (get-in % [:some-key :another-key])) my-map)

And if your list could potentially have nil items:

(filter #(= "val" (:another-key (:some-key %))) my-map)

Explanation:

(:k nil);; => nil
(nil :k);; => NPE
({:k 4} :k);; => 4
(:k {:k 4});; => 4
;; BTW, you can also specify the "not found" case:
(:k nil :not-there);; => :not-there

See also the clojure style guide.

Upvotes: 2

Related Questions