简体   繁体   English

使用Java对象作为Clojure映射

[英]Use Java object as Clojure map

I have a Java class that I'd like to use in Clojure. 我有一个我想在Clojure中使用的Java类。 But, I want to use it as a Clojure map. 但是,我想将它用作Clojure地图。 What are the steps required to do so? 这样做的步骤是什么?

I've looked at the code for IPersistentMap -- should the Java class implement that? 我查看了IPersistentMap的代码 - Java类应该实现吗? Or should there be some Clojure code which implements a protocol? 或者是否应该有一些实现协议的Clojure代码?

I know I could just write some mapping code, to explicitly convert the code from Java objects to maps, but that solution has a high effort/reward ratio. 我知道我可以编写一些映射代码,将代码从Java对象显式转换为映射,但该解决方案具有很高的努力/回报率。 Also, I might encounter this same situation many more times. 此外,我可能会多次遇到同样的情况。


Concrete example: I have a parser written in Java. 具体示例:我有一个用Java编写的解析器。 I'd like to use that to parse some text, and then access the contents of the parsed data structure as though it were in Clojure maps: 我想使用它来解析一些文本,然后访问解析数据结构的内容,就像它在Clojure映射中一样:

(def parser (new MyParser))

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

The function bean came to mind: 想到函数bean

Takes a Java object and returns a read-only implementation of the map abstraction based upon its JavaBean properties. 获取Java对象并基于其JavaBean属性返回映射抽象的只读实现。

Example taken from the site: 从网站上取的示例:

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}

Sure the (bean javaObject) (see bean ClojureDoc ) works well, but it doesn't let you select the property you want and those you doesn't. 当然(bean javaObject) (请参阅bean ClojureDoc )运行良好,但它不允许您选择所需的属性和不需要的属性。 It has impact when you input the resulting map into the json-str function, in that case you can get an error saying : "Don't know how to write JSON of ..." 将结果映射输入到json-str函数时会产生影响,在这种情况下,您可能会收到错误消息:“不知道如何编写JSON ...”

And I find that annoying when I deal with NoSQL DB (mongoDB, neo4j) that accepts essentially JSON (like the underlying of neocons ). 而且我发现恼人的,当我处理基本上接受JSON(像底层的NoSQL数据库(MongoDB中,Neo4j的) 新保守主义者 )。

So what's my solution? 那么我的解决方案是什么?

(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) #"\\."))))

And you can use it like that (yes the function takes care of a chain of property if any) 并且你可以像这样使用它(是的,如果有的话,函数会处理一系列属性)

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

Hope that will help someone... 希望能帮助别人......

Clojure keywords can look up stuff in anything that implements the required (read-only) parts of the java.lang.Map interface. Clojure关键字可以查找实现java.lang.Map接口所需(只读)部分的任何内容。 The problem is probably going to be that you're not actually using clojure keywords as keys so that might not help you. 问题可能是你实际上没有使用clojure关键字作为键,所以这可能对你没有帮助。

As for IPersistentMap; 至于IPersistentMap; your parser presumably doesn't implement anything relevant to the that interface. 你的解析器可能没有实现与该接口相关的任何东西。

Personally, I'd write a straight up conversion function. 就个人而言,我会写一个直接转换函数。 Clojure uses a lot of those (seq, for instance) and after converting, you know you're dealing with a real persistent map and not something that only acts like it some of the time (so you can actually call seq, keys, vals etc on it). Clojure使用了很多(例如seq),并且在转换之后,你知道你正在处理一个真正的持久性地图,而不是某些时候只是像它一样的东西(所以你实际上可以调用seq,keys,vals)等等)。

Alternatively; 另外;

  • just implement clojure.lang.ILookup, and leave out everything else. 只需实现clojure.lang.ILookup,并省略其他所有内容。
  • convert using some generated/reflection code if you want something more generic. 如果你想要更通用的东西,使用一些生成/反射代码转换。 See https://github.com/joodie/clj-java-fields for an example. 有关示例,请参阅https://github.com/joodie/clj-java-fields

What about just using a java.util.HashMap with (interned) strings as keys, and doing the conversion in a few lines of Clojure ?: 那么只使用带有(实习)字符串的java.util.HashMap作为键,并在几行Clojure中进行转换?:

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

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

or with keywords: 或与关键字:

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

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

bean works fine, but it does not handle some Java objects very well. bean工作正常,但它不能很好地处理一些Java对象。

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

but there is a java solution to this java problem: 但是这个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}
user=> (defn parser [text]
  "{ :items { \"itemid\" 55 }}");Mock
user=> (let [parse-tree (read-string (parser "Abracadabra"))]
((parse-tree :items) "itemid"))
55

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM