0

I have a Java class generated by Protocol Buffers called TextLine. When I instantiate the Java object with:

(def tb (-> (TextLine/newBuilder) (.setText "this is a text line") (.build)))

And then call:

(from-java tb)

I receive a StackOverflowError:

java.lang.StackOverflowError: null
 at java.lang.Class.getMethods (Class.java:1614)
clojure.lang.Reflector.getMethods (Reflector.java:373)
clojure.lang.Reflector.invokeNoArgInstanceMember (Reflector.java:311)
clojure.java.data$add_getter_fn.invokeStatic (data.clj:38)
clojure.java.data$add_getter_fn.invoke (data.clj:37)
clojure.core.protocols$fn__6755.invokeStatic (protocols.clj:167)
clojure.core.protocols/fn (protocols.clj:124)
clojure.core.protocols$fn__6710$G__6705__6719.invoke (protocols.clj:19)
clojure.core.protocols$seq_reduce.invokeStatic (protocols.clj:31)
clojure.core.protocols$fn__6732.invokeStatic (protocols.clj:75)
clojure.core.protocols/fn (protocols.clj:75)
clojure.core.protocols$fn__6684$G__6679__6697.invoke (protocols.clj:13)
clojure.core$reduce.invokeStatic (core.clj:6545)
clojure.core$reduce.invoke (core.clj:6527)
clojure.java.data$eval554$fn__555.invoke (data.clj:135)
clojure.lang.MultiFn.invoke (MultiFn.java:229)
clojure.java.data$make_getter_fn$fn__501.invoke (data.clj:35)
clojure.java.data$eval554$fn__555$iter__556__560$fn__561.invoke (data.clj:136)
clojure.lang.LazySeq.sval (LazySeq.java:40)
clojure.lang.LazySeq.seq (LazySeq.java:49)
clojure.lang.Cons.next (Cons.java:39)
clojure.lang.RT.next (RT.java:688)
clojure.core$next__4341.invokeStatic (core.clj:64)
clojure.core.protocols$fn__6755.invokeStatic (protocols.clj:168)
clojure.core.protocols/fn (protocols.clj:124)
clojure.core.protocols$fn__6710$G__6705__6719.invoke (protocols.clj:19)
clojure.core.protocols$seq_reduce.invokeStatic (protocols.clj:31)
clojure.core.protocols$fn__6738.invokeStatic (protocols.clj:75)
clojure.core.protocols/fn (protocols.clj:75)
clojure.core.protocols$fn__6684$G__6679__6697.invoke (protocols.clj:13)
clojure.core$reduce.invokeStatic (core.clj:6545)
clojure.core$into.invokeStatic (core.clj:6610)
clojure.core$into.invoke (core.clj:6604)
clojure.java.data$eval554$fn__555.invoke (data.clj:136)
clojure.lang.MultiFn.invoke (MultiFn.java:229)
clojure.java.data$make_getter_fn$fn__501.invoke (data.clj:35)
clojure.java.data$eval554$fn__555$iter__556__560$fn__561.invoke (data.clj:136)
clojure.lang.LazySeq.sval (LazySeq.java:40)
clojure.lang.LazySeq.seq (LazySeq.java:49)
clojure.lang.Cons.next (Cons.java:39)
clojure.lang.RT.next (RT.java:688)
clojure.core$next__4341.invokeStatic (core.clj:64)
clojure.core.protocols$fn__6755.invokeStatic (protocols.clj:168)
clojure.core.protocols/fn (protocols.clj:124)
clojure.core.protocols$fn__6710$G__6705__6719.invoke (protocols.clj:19)
clojure.core.protocols$seq_reduce.invokeStatic (protocols.clj:31)
clojure.core.protocols$fn__6738.invokeStatic (protocols.clj:75)
clojure.core.protocols/fn (protocols.clj:75)
clojure.core.protocols$fn__6684$G__6679__6697.invoke (protocols.clj:13)
clojure.core$reduce.invokeStatic (core.clj:6545)
clojure.core$into.invokeStatic (core.clj:6610)
clojure.core$into.invoke (core.clj:6604)
clojure.java.data$eval554$fn__555.invoke (data.clj:136)
clojure.lang.MultiFn.invoke (MultiFn.java:229)
clojure.java.data$make_getter_fn$fn__501.invoke (data.clj:35)
clojure.java.data$eval554$fn__555$iter__556__560$fn__561.invoke (data.clj:136)
clojure.lang.LazySeq.sval (LazySeq.java:40)
clojure.lang.LazySeq.seq (LazySeq.java:49)
clojure.lang.Cons.next (Cons.java:39)
clojure.lang.RT.next (RT.java:688)
clojure.core$next__4341.invokeStatic (core.clj:64)
clojure.core.protocols$fn__6755.invokeStatic (protocols.clj:168)
clojure.core.protocols/fn (protocols.clj:124)
clojure.core.protocols$fn__6710$G__6705__6719.invoke (protocols.clj:19)
....

Any ideas on what could be causing this or the best way to troubleshoot it? I'd really like to interface with the Java object as a Clojure map.

frank
  • 501
  • 8
  • 21

1 Answers1

3

I wouldn't recommend using clojure.data.java/from-java for much of anything. The idea that simple function can translate an arbitrary Java object into a reasonable Clojure map without any domain knowledge of the source object is wishful thinking.

I hadn't heard of it before today, but I went and looked at the source and as expected it is basically just an extension of clojure.core/bean, another hopeful attempt at an impossible problem. Specifically, it uses javabean introspection to try to guess what getters and setters represent meaningful fields. Now, However, like many Java classes that weren't designed to be used as beans, protobuf classes contain circular references, which means that recursively bean-ing them up is an infinite task, leading eventually to a stack overflow.

What to do instead? I would recommend just working with the generated Java protobuf classes through Java interop, or perhaps trying to find a good Clojure protobuf library. Don't try to convert the Java objects into idiomatic Clojure data.

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • Thanks for that! I'm sure you saved me days of frustration. I found what appears to be a reputable [Clojure protobuf library](https://github.com/ninjudd/clojure-protobuf). Would you happen to know if it supports proto3? Also, based on your experience, would it be better just to work with the Java protobuf classes instead of something like this library? – frank Jun 20 '17 at 14:03
  • A few years ago I wrote a piece of code that could convert more-or-less generic Apache Thrift object trees into Clojure data structures. Instead of enumerating fields in generated classes, I used the metadata that was available in some Thrift-generated object. I could get the "business" fields and their types for each class. Thus I avoided extra fields that were there for Thrift's need only. Perhaps a similar approach could be applied to Protobuf as well. – ez121sl Jun 20 '17 at 16:05
  • @ez121sl Yes, that is precisely what a good protobuf library for Clojure would do, and it works because protobuf, like thrift, has enough metadata to describe the object meaningfully. – amalloy Jun 21 '17 at 02:42
  • I tried unsuccessfully to get [clojure-protobuf](https://github.com/ninjudd/clojure-protobuf) working with *proto3*. The last commit was October 2013. If performance is not a concern, why not just de/serialize from/to JSON and into a Clojure map? It seems like that will be the simplest solution to work with idiomatic Clojure structures. – frank Jun 22 '17 at 15:15