Search

July 14, 2020

Clojure Goodness: Turn Java Object To Map With bean Function

The map data structure is used a lot in Clojure. When we want to use Java objects in our Clojure code we can convert the Java object to a map with the bean function. This function will use reflection to get all the properties of the Java object and converts each property with the property value to a key with value in the resulting map. The bean function will not recursively convert nested objects to a map, only the top-level properties are turned into key value pairs.

We see several examples of using the bean function in the following code snippet:

(ns mrhaki.core.bean
  (:require [clojure.test :refer [is]])
  (:import (java.net URI)
           (mrhaki.java Person Address)))

(is (= {:path "",
        :rawQuery "search=clojure",
        :fragment nil,
        :authority "www.mrhaki.com",
        :rawAuthority "www.mrhaki.com",
        :port -1,
        :absolute true,
        :host "www.mrhaki.com",
        :rawPath "",
        :opaque false,
        :rawSchemeSpecificPart "//www.mrhaki.com?search=clojure",
        :class java.net.URI,
        :rawUserInfo nil,
        :query "search=clojure",
        :rawFragment nil,
        :scheme "https",
        :userInfo nil,
        :schemeSpecificPart "//www.mrhaki.com?search=clojure"}
       (bean (URI. "https://www.mrhaki.com?search=clojure"))))


(comment "For the next sample we use a Java class Person
  with properties name and alias of type String and
  a third property of type Address. The Java class Address
  has a single String property city.
  
  Pseudo code: 
  class Person { String name, alias; Address address; }
  class Address { String city; }")

(def person (Person. "Hubert" "mrhaki" (Address. "Tilburg")))

(is (= {:name "Hubert" :alias "mrhaki"} 
       (select-keys (bean person) [:name :alias])))

;; Properties with custom classes are not automatically
;; also converted to a map representation.
(is (instance? mrhaki.java.Address (:address (bean person))))

(is (= {:city "Tilburg"}
       (select-keys (bean (:address (bean person))) [:city])))


(is (= {:address {:city "Tilburg", :class mrhaki.java.Address},
        :alias "mrhaki",
        :class mrhaki.java.Person,
        :name "Hubert"}
       (assoc (bean person) :address (bean (.getAddress person)))))

Written with Clojure 1.10.1.