Построение карты и dissoc
использование ключей, на которые вы хотите наложить условия на основе предиката (здесь - nil?
), может быть самым простым подходом (NB. Эта функция только проверяет ключи, явно упомянутые в качестве аргументов;те, которые не упомянуты, никогда не удаляются, независимо от того, соответствуют ли значения, прикрепленные к ним, предикату или нет):
(defn dissoc-when
"Dissoc those keys from m which are mentioned among ks and whose
values in m satisfy pred."
[pred m & ks]
(apply dissoc m (filter #(pred (m %)) ks)))
При REPL:
user> (dissoc-when nil? {:foo nil :bar true :quux nil} :foo :bar)
{:quux nil, :bar true}
Хотя, вообще говоря, если вы ожидаете, чтоработать с большим количеством карт, представляющих сущности реального мира определенного типа, вы можете перейти к записям - и тогда вы можете просто пропустить все nil
s на этапе, на котором вы извлекаете значения из входной карты, потому что записи,при просмотре в виде карт всегда отображаются ключи, соответствующие их полям.Например,
(defrecord Person [username first-name last-name])
Затем вы можете выделить логику для "преобразования схемы" между картами:
(defn translate-map
"Transforms the data map in accordance with the spec in table.
Skips nil-valued entries."
[data table]
(->> table
(keep (fn [[to from]]
(when-let [from-val (get-in data from)]
[to from-val])))
(into {})))
Теперь ваша функция create-record
становится композицией translate-map
и map->Person
:
(defn create-person [data]
(map->Person
(translate-map data {:username [:username]
:first-name [:user-info :name :first]
:last-name [:user-info :name :last]
:gender [:user-info :sex]})))
Если вы предпочитаете работать с обычными картами, вместо эквивалентного вывода вы можете использовать что-то вроде следующего:
(defn create-person [data]
(merge (zipmap [:username :first-name :last-name] (repeat nil))
(translate-map data {:username [:username]
:first-name [:user-info :name :first]
:last-name [:user-info :name :last]
:gender [:user-info :sex]})))
На REPL (версия записи в Clojure 1.3):
user> (create-person {:username "jsmith"
:user-info {:name {:first "John" :last "Smith"}}})
#user.Person{:username "jsmith", :first-name "John", :last-name "Smith"}
user> (create-person {:username "jsmith"
:user-info {:name {:first "John" :last "Smith"}
:sex :male}})
#user.Person{:username "jsmith", :first-name "John", :last-name "Smith", :gender :male}