Search code examples
clojuredestructuring

clojure - presence of a given set of keys in a clojure map


There is a map like this

{:buyers [{:name "James" :city "Berlin"} {:name "Jane" :city "Milan"}]
:sellers [{:name "Dustin" :city "Turin" :age "21"} {:name "Mark" :city "Milan"}]}

and I need to check only for :sellers that all the keys :name, :city and :age are present and if one is missing drop that map all together and have a new structure as below:

{:buyers [{:name "James" :city "Berlin"} {:name "Jane" :city "Milan"}]
:sellers [{:name "Dustin" :city "Turin" :age "21"}]}

I came across validateur and I am trying to use it like:

(:require [validateur.validation :as v])

(def my-map {:buyers [{:name "James" :city "Berlin"} {:name "Jane" :city "Milan"}]
             :sellers [{:name "Dustin" :city "Turin" :age "21"} {:name "Dustin" :city "Milan" :age "" } {:city "Rome" :age "22"}]})

(defn valid-seller? [mp]
  (let [v-set (v/validation-set
               (v/presence-of #{:name :city :age}))]  
    (fmap vec (v-set mp))))

(map valid-seller? (:sellers my-map))

=> ({} {:age ["can't be blank"]} {:name ["can't be blank"]})

But I do not know how to update my map so missing keys or nil values be dropped


Solution

  • To make the code more readable, I created a new predicate, valid-seller?, and put validation there. You can use any of these versions:

    Pure Clojure:

    (defn valid-seller? [m]
      (every? #(contains? m %) [:name :city :age]))
    

    Spec:

    [org.clojure/spec.alpha "0.3.218"], require [clojure.spec.alpha :as spec]

    (defn valid-seller? [m]
      (spec/valid? (spec/keys :req-un [::name ::city ::age]) m))
    

    Malli (if you also want to test type of values):

    [metosin/malli "0.8.9"], require [malli.core :as malli]

    (defn valid-seller? [m]
      (malli/validate [:map
                       [:name :string]
                       [:city :string]
                       [:age :string]] m))
    

    Then I used this predicate:

    (update {:buyers [{:name "James" :city "Berlin"} {:name "Jane" :city "Milan"}]
             :sellers [{:name "Dustin" :city "Turin" :age "21"} {:name "Mark" :city "Milan"}]}
            :sellers
            #(filter valid-seller? %))
    =>
    {:buyers [{:name "James", :city "Berlin"} {:name "Jane", :city "Milan"}],
     :sellers ({:name "Dustin", :city "Turin", :age "21"})}
    

    After your answer, I think you should use Malli, as it also checks the type of values. You can use some? for any non-nil value:

    (defn valid-seller? [m]
      (malli/validate [:map
                       [:name some?]
                       [:city some?]
                       [:age some?]] m))