Использовать объект Java в качестве карты Clojure - PullRequest
16 голосов
/ 27 октября 2011

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

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

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


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

(def parser (new MyParser))

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

Ответы [ 6 ]

22 голосов
/ 27 октября 2011

На ум пришла функция 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}
5 голосов
/ 26 августа 2012

Конечно, (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 ~@ is here to unsplice the few properties
       (map (fn [prop] [(keyword (str prop)) `(.. ~object ~@(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)

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

1 голос
/ 27 октября 2011

Как насчет использования 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"}
1 голос
/ 27 октября 2011

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

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

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

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

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

bean работает нормально, но не очень хорошо обрабатывает некоторые объекты Java.

(import java.awt.Insets)
(bean (Insets. 1 2 3 4))
=> {:class java.awt.Insets}

но есть решение Java для этой проблемы Java:

(import (com.fasterxml.jackson.databind ObjectMapper))
(import (java.util Map))
(into {} (.. (ObjectMapper.) (convertValue (Insets. 1 2 3 4) Map)))
=> {"top" 1, "left" 2, "bottom" 3, "right" 4}
0 голосов
/ 27 октября 2011
user=> (defn parser [text]
  "{ :items { \"itemid\" 55 }}");Mock
user=> (let [parse-tree (read-string (parser "Abracadabra"))]
((parse-tree :items) "itemid"))
55
...