In order for this to work, your Java objects need to conform to the JavaBean specification. This means they need methods .getXXX()
to read object properties (at least), and also .setXXX()
to construct a new object. Example:
Class Inner
:
package demo;
public class Inner {
public String secret;
public String getSecret() {
return secret;
}
public Inner(String arg) {
this.secret = arg;
}
}
Class Outer
:
package demo;
import java.util.HashMap;
import demo.Inner;
public class Outer {
public HashMap<String, Inner> someMap;
public Outer() {
HashMap<String,Inner> hm = new HashMap<String, Inner>();
hm.put("stuff", new Inner( "happens"));
hm.put("another", new Inner( "thing"));
this.someMap = hm;
}
public HashMap getSomeMap() { return someMap; }
}
and Clojure code to decode the nested JavaBean objects:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require
[clojure.java.data :as jd])
(:import [demo Calc]))
(dotest
(let [java-obj (Outer.)
obj-shallow (jd/from-java java-obj)
obj-deep (jd/from-java-deep java-obj {})]
(spyx java-obj)
(spyx obj-shallow)
(spyx-pretty obj-deep)
))
The results show what happens:
--------------------------------------
Clojure 1.10.2-alpha1 Java 14
--------------------------------------
lein test tst.demo.core
java-obj => #object[demo.Outer 0x138d8219 "demo.Outer@138d8219"]
obj-shallow => {:someMap {"another" #object[demo.Inner 0x8d86c4d "demo.Inner@8d86c4d"], "stuff" #object[demo.Inner 0x28c92c51 "demo.Inner@28c92c51"]}}
obj-deep => {:someMap {"another" {:secret "thing"},
"stuff" {:secret "happens"}}}
The raw java-obj
is opaque to Clojure. Using jd/from-java
only unpacks the outer layer using the JavaBean getters. Using jd/from-java-deep
(notice the required options map, left empty here) will recursively unpack the JavaBean using the appropriate getters on each object based on its java class.
All of the above code is based on this template project. Enjoy!