convert Java objects to Clojure types

818 views Asked by At

Hi Currently I am using java.data (https://github.com/clojure/java.data) to convert java pojos to clojure compatible types. It does not work for nested objects.

For ex:

class Abc {
   Map<String, Def> someMap;
}

Class Def {
   String b;
}

If I pass sample instance of Abc to java.data, I get the output as:

   {
      :someMap { 
         "keyString" #object[com.sample.Def 0xb33584d "com.sample.Def@b33584d"]
      }
   }

But I want the output as:

   {
       :someMap { 
            "keyString" {
                 "b" "value" 
            } 
       }
   }

How can I fix this?

I tried clojure.core bean (https://clojuredocs.org/clojure.core/bean) and it dint seem to work as well.

Thank you in advance.

2

There are 2 answers

0
Alan Thompson On BEST ANSWER

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!

0
Sean Corfield On

Clojure's built-in bean function only does a shallow conversion -- it doesn't recursively convert any nested information. Check out https://github.com/clojure/java.data for a recursive conversion from Java objects to Clojure data structures (and back).

java.data also support "builder"-style Java APIs so you can build Java objects more easily from Clojure.