Использовать объект Java как карту Clojure

У меня есть класс Java, который я хотел бы использовать в Clojure. Но я хочу использовать его как карту Clojure. Каковы шаги, необходимые для этого?

Я просмотрел код для IPersistentMap - должен ли Java-класс реализовать это? Или должен быть какой-то Clojure код, который реализует протокол?

Я знаю, что могу просто написать код сопоставления, чтобы явно преобразовать код из объектов Java в карты, но это решение имеет большое соотношение усилий/вознаграждений. Кроме того, я мог бы столкнуться с такой же ситуацией много раз.


Конкретный пример: у меня есть парсер, написанный на Java. Я хотел бы использовать это для разбора некоторого текста, а затем получить доступ к содержимому анализируемой структуры данных, как если бы он был в Clojure maps:

(def parser (new MyParser))

(let [parse-tree (parser ... parse some text ...)]
  ((parse-tree :items) "itemid"))

Ответ 1

Функция bean пришла в голову:

Принимает объект Java и возвращает реализацию абстракции карты только для чтения на основе ее свойств JavaBean.

Пример, взятый с сайта:

user=> (import java.util.Date)
java.util.Date

user=> (def *now* (Date.))
#'user/*now*

user=> (bean *now*)
{:seconds 57, :date 13, :class java.util.Date,
 :minutes 55, :hours 17, :year 110, :timezoneOffset -330,
 :month 6, :day 2, :time 1279023957492}

Ответ 2

Уверен, что (bean javaObject) (см. bean ClojureDoc), но он не позволяет вам выбрать требуемое свойство и те, которых нет. Это влияет на ввод результирующей карты в функцию json-str, в этом случае вы можете получить сообщение об ошибке: "Не знаю, как писать JSON of..."

И я нахожу, что это раздражает, когда я имею дело с NoSQL DB (mongoDB, neo4j), который принимает по существу JSON (например, лежащую в основе neocons).

Так что же мое решение?

(defmacro get-map-from-object-props [object & props]
  ;->> will eval and reorder the next list starting from the end
  (->> (identity props) ;identity is here to return the 'props' seq
       ;map each property with their name as key and the java object invocation as the value
       ;the [email protected] is here to unsplice the few properties
       (map (fn [prop] [(keyword (str prop)) `(.. ~object [email protected](prop-symbol prop) )]))
       (into {})))

;getter is a simple function that transform a property name to its getter "name" -> "getName"
(defn prop-symbol [prop]
  (map symbol (map getter (clojure.string/split (str prop) #"\\."))))

И вы можете использовать его так (да функция выполняет целую цепочку свойств, если таковая имеется)

(get-map-from-object-props javaObject property1 property2 property3.property1)

Надеюсь, что это поможет кому-то...

Ответ 3

Clojure ключевые слова могут искать материал во всем, что реализует необходимые (только для чтения) части интерфейса java.lang.Map. Проблема, вероятно, состоит в том, что вы на самом деле не используете clojure ключевые слова в качестве ключей, чтобы это не помогло вам.

Что касается IPersistentMap; ваш синтаксический анализатор, по-видимому, не реализует ничего, что имеет отношение к этому интерфейсу.

Лично я бы написал прямое преобразование. clojure использует много таких (seq, например), и после конвертирования вы знаете, что имеете дело с реальной постоянной картой, а не с тем, что действует только некоторое время (так что вы действительно можете вызвать seq, keys, vals и т.д.).

В качестве альтернативы;

  • просто выполните clojure.lang.ILookup и оставьте все остальное.
  • конвертировать, используя какой-то сгенерированный/отраженный код, если вы хотите что-то более общее. См. https://github.com/joodie/clj-java-fields для примера.

Ответ 4

Как просто использовать java.util.HashMap с (интернированными) строками в качестве ключей и выполнить преобразование в нескольких строках Clojure?:

(into {} (java.util.HashMap. {"foo" "bar" "baz" "quux"})) ?

{"foo" "bar" "baz" "quux"}

или с ключевыми словами:

(into {}
  (map
    (juxt
      #(keyword (key %))
      #(val %))
    (java.util.HashMap. {"foo" "bar" "baz" "quux"})))

{:baz "quux", :foo "bar"}

Ответ 5

user=> (defn parser [text]
  "{ :items { \"itemid\" 55 }}");Mock
user=> (let [parse-tree (read-string (parser "Abracadabra"))]
((parse-tree :items) "itemid"))
55