На мой вопрос можно было бы легко ответить, но я только начал изучать clojure и понятия не имею, как это сделать.

У меня есть файл JSON, который я прочитал и проанализировал. Теперь мне нужно получить каждый элемент этого JSON и обработать его в соответствии с типом. Например, у меня есть JSON:

{
  "foo": {
    "id": 1,
    "name": "foo1"
  },
  "bar": {
    "id": 1,
    "name": "bar1"
  },
  "foo": {
    "id": 2,
    "name": "foo2"
  },
  "bar": {
    "id": 2,
    "name": "bar2"
  }
}

Итак, я хочу создать функцию, которая выполняет итерацию для каждого элемента моего JSON, а затем вызывает другую функцию с множественными объявлениями для каждого типа элемента моего JSON.

Проблема в том, что я не знаю, как получить тип элемента JSON ... Кто-нибудь может мне помочь?

1
Max Forasteiro 13 Мар 2018 в 18:17

2 ответа

Лучший ответ

Вы можете получить все пары "ключ-значение" карты, рассматривая ее как последовательность Clojure. Затем он ведет себя как последовательность двухэлементных векторов, а точнее последовательность элементов MapEntry. Когда вы позвоните first на MapEntry, вы получите ключ.

Итак, (first (seq {:a 1})) возвращает [:a 1], а (first [:a 1]) возвращает :a, ключ. Вызов seq в этом примере не нужен, он нужен только для того, чтобы сделать пример более явным.

Затем вы можете написать выражение case для выполнения действий в соответствии с ключом. Чтобы сделать его более расширяемым, вы можете использовать мультиметоды.

(def json-str "{\n  \"foo\": {\n    \"id\": 1,\n    \"name\": \"foo1\"\n  },\n  \"bar\": {\n    \"id\": 1,\n    \"name\": \"bar1\"\n  },\n  \"foo\": {\n    \"id\": 2,\n    \"name\": \"foo2\"\n  },\n  \"bar\": {\n    \"id\": 2,\n    \"name\": \"bar2\"\n  }\n}")

(def parsed (cheshire.core/parse-string json-str true))
;; {:foo {:id 2, :name "foo2"}, :bar {:id 2, :name "bar2"}}

;; handle with case:

(defn handle-data
  [data]
  (doseq [[k v] data]
    (case k
      :foo (println "this is a foo!" v)
      :bar (println "this is a bar!" v)
      (throw (ex-info (str "Did not recognize key "
                           key)
                      {:key k
                       :map v})))))

;; handle with multimethods:

(defmulti do-thing-depending-on-key first)

(defmethod do-thing-depending-on-key :foo [[k v]]
  (println "this is a foo!" v))

(defmethod do-thing-depending-on-key :bar [[k v]]
  (println "this is a bar!" k))

(defmethod do-thing-depending-on-key :default [[k v]]
  (throw (ex-info (str "Did not recognize key "
                       key)
                  {:key k
                   :map v})))

(run! do-thing-depending-on-key parsed)
;; this is a foo! {:id 2, :name foo2}
;; this is a bar! {:id 2, :name bar2}
;; => nil

(run! do-thing-depending-on-key {:unknown {:id 3 :name "Unknown"}})
;; => 
;; clojure.lang.ExceptionInfo:
;; Did not recognize key :unknown {:key :unknown, :map {:id 3, :name "Unknown"}}
3
Michiel Borkent 13 Мар 2018 в 16:07

Мультиметоды могут оказаться излишними для чего-то столь простого. Я бы просто использовал cond:

(ns tst.demo.core
  (:require
    [cheshire.core :as cheshire]
    [clojure.java.io :as io] ))

  (let [json-str (slurp (io/resource "data.json"))
        edn-data (cheshire/parse-string json-str true) ; need `true` to emit keywords
        ]
    (doseq [[type id-name-map] edn-data]
      (cond
        (= type :foo) (println :foo-proc id-name-map)
        (= type :bar) (println :bar-proc id-name-map)
        :else         (println :other-proc id-name-map) )))

С результатами:

:foo-proc {:id 2, :name foo2}
:bar-proc {:id 2, :name bar2}

Для чего в project.clj требуется следующее:

  :dependencies [
    [cheshire "5.8.0"]
      ....

И где ваши данные находятся в resources/data.json:

~/expr/demo > cat resources/data.json 
{
  "foo": {
    "id": 1,
    "name": "foo1"
  },
  "bar": {
    "id": 1,
    "name": "bar1"
  },  ....
1
Alan Thompson 13 Мар 2018 в 16:04