Clojure: java interop with bean
One of the great things with Clojure is that it has fantastic java interop. In this article we explore the bean
function and how it makes working with java objects more idiomatic.
First, we import a java library. For this example we use googlei18n/libphonenumber a google library for processing phone numbers. We want to use it to split a phone number into different parts (country code and national number):
(import [com.google.i18n.phonenumbers PhoneNumberUtil])
Let's parse a phone number:
(.parse (PhoneNumberUtil/getInstance) "+41446681800" "")
=>
#object[com.google.i18n.phonenumbers.Phonenumber$PhoneNumber 0x57762fc2 "Country Code: 41 National Number: 446681800"]
We get an object back. The string name of the object implies that it might contain what we are interested in (country code and national number).
Without Bean
Let's see if the object contains a method for extracting the country code:
(->> (.getDeclaredMethods com.google.i18n.phonenumbers.Phonenumber$PhoneNumber)
(map #(.getName %))
(filter #(clojure.string/includes? % "Code")))
=>
("hashCode"
"hasPreferredDomesticCarrierCode"
"clearCountryCode"
"clearPreferredDomesticCarrierCode"
"getCountryCode"
"setCountryCode"
"clearCountryCodeSource"
"setPreferredDomesticCarrierCode"
"setCountryCodeSource"
"getPreferredDomesticCarrierCode"
"hasCountryCodeSource"
"getCountryCodeSource"
"hasCountryCode")
And a method for extracting the national number:
(->> (.getDeclaredMethods com.google.i18n.phonenumbers.Phonenumber$PhoneNumber)
(map #(.getName %))
(filter #(clojure.string/includes? % "Number")))
=>
("hasNationalNumber"
"hasNumberOfLeadingZeros"
"clearNationalNumber"
"clearNumberOfLeadingZeros"
"setNumberOfLeadingZeros"
"setNationalNumber"
"getNumberOfLeadingZeros"
"getNationalNumber")
We can now use .getCountryCode
and .getNationalNumber
to build a function that splits the phone number into its different parts:
(let [number-obj (.parse (PhoneNumberUtil/getInstance) "+41446681800" "")
countryCode (.getCountryCode number-obj)
nationalNumber (.getNationalNumber number-obj)]
{:phone/full-number (str "+" countryCode nationalNumber)
:phone/prefix (str "+" countryCode)
:phone/number-no-prefix (str nationalNumber)})
=>
#:phone{:full-number "+41446681800",
:prefix "+41",
:number-no-prefix "446681800"}
With Bean
The bean
function converts a java object into a clojure map:
(bean (.parse (PhoneNumberUtil/getInstance) "+41446681800" ""))
=>
{:numberOfLeadingZeros 1,
:preferredDomesticCarrierCode "",
:extension "",
:italianLeadingZero false,
:class com.google.i18n.phonenumbers.Phonenumber$PhoneNumber,
:countryCodeSource
#object[com.google.i18n.phonenumbers.Phonenumber$PhoneNumber$CountryCodeSource 0x2724e628 "UNSPECIFIED"],
:nationalNumber 446681800,
:rawInput "",
:countryCode 41}
This allows us to use all the tools of the language (in this case destructuring) to extract the data we care about:
(let [{:keys [countryCode nationalNumber]}
(bean (.parse (PhoneNumberUtil/getInstance) "+41446681800" ""))]
{:phone/full-number (str "+" countryCode nationalNumber)
:phone/prefix (str "+" countryCode)
:phone/number-no-prefix (str nationalNumber)})
=>
#:phone{:full-number "+41446681800",
:prefix "+41",
:number-no-prefix "446681800"}
Much simpler!
This covers using the bean
function and how it makes working with java object more idiomatic.