Я использую Datomic, хотя это не имеет особого значения для этого вопроса. Но обычно он возвращает ключи пространства имен (и значения перечисления возвращаются как ключевые слова пространств имен). Я хочу перевести потенциально вложенную структуру, чтобы убрать пространства имен из ключей и из значений (а также из строковых значений перечислений). Я делаю это, потому что я возвращаю результат в JSON REST API, а пространство имен в этом контексте не имеет особого смысла. Вот простой пример структуры:

{ 
    :person/name "Kevin"
    :person/age 99
    :person/gender :gender/M
    :person/address {
        :address/state :state/NY
        :address/city "New York"
        :address/zip "99999"
    }
}

И я надеюсь перевести на:

{ 
    :name "Kevin"
    :age 99
    :gender "M"
    :address {
        :state "NY"
        :city "New York"
        :zip "99999"
    }
}

Я знаю, что могу сделать одну вещь - использовать (postwalk-replace {:person/name :name :person/age :age :person/gender :gender :person/address :address :address/city :city :address/state :state :address/zip :zip} the-entity), и это касается ключей, но не значений.

Какие еще варианты у меня есть?

2
Kevin 27 Май 2017 в 21:23

2 ответа

Лучший ответ

Вы можете использовать clojure.walk/postwalk. Простая версия не различает ключевые слова как ключи или значения на карте, просто конвертирует все ключи в строки:

(def data {:person/name "Kevin"
           :person/age 99
           :person/gender :gender/M
           :person/address {:address/state :state/NY
                            :address/city "New York"
                            :address/zip "99999"}})


(clojure.walk/postwalk
  (fn [x]
    (if (keyword? x)
      (name x)
      x))
  data)

;; => => {"name" "Kevin", "age" 99, "gender" "M", "address" {"state" "NY", "city" "New York", "zip" "99999"}}

Чтобы реализовать именно то, что вам нужно, вам нужно обрабатывать ключи и значения на карте отдельно:

(defn transform-keywords [m]
  (into {}
        (map (fn [[k v]]
               (let [k (if (keyword? k) (keyword (name k)) k)
                     v (if (keyword? v) (name v) v)]
                 [k v]))
             m)))

(clojure.walk/postwalk
  (fn [x]
    (if (map? x)
      (transform-keywords x)
      x))
  data)

;; => => {:name "Kevin", :age 99, :gender "M", :address {:state "NY", :city "New York", :zip "99999"}}
3
Piotrek Bzdyl 27 Май 2017 в 19:06

Как примечание: по моему опыту, несоответствие импеданса между ключами с пространством имен и без него на границе вашей системы может быть постоянной болью; более того, наличие ключей с квалификацией пространства имен имеет значительные преимущества в отношении ясности кода (очень хорошая отслеживаемость данных).

Так что я бы не стал слишком быстро отказываться от ключей с именами. Если синтаксис EDN для пространства имен (с точками и косыми чертами) не подходит потребителям вашего API, вы можете даже использовать что-то более традиционное, например подчеркивание (например, :person_name вместо :person/name); немного страшнее, но все же дает вам большинство преимуществ ключей, квалифицированных в пространстве имен, вам даже не потребуется преобразовывать структуры данных, и Datomic не будет возражать.

3
Valentin Waeselynck 27 Май 2017 в 20:14