0

Using GraalVM, to expose Java objects to JavaScript, I am using ProxyObject to wrap them. For this purpose, I am using ProxyObject.fromMap method like the following:

ProxyObject exposedObject = ProxyObject.fromMap(objectMapper.convertValue(javaObject, Map.class));

Here, the javaObject is of Object class and can be arbitrarily complex. This method works for immediate members of javaObject, but not when the members are complex objects themselves. For example, if one of the members of javaObject happens to be a Map, like:

        final Map<String, Object> source = new HashMap<>();
        source.put("id", "1234567890");
        final Map<String, Object> sourceComponent = ImmutableMap.of("key", "value");
        source.put("complex", sourceComponent);

        // assuming the source is any object   
        ProxyObject exposedObject = ProxyObject.fromMap(objectMapper.convertValue(source, Map.class));

        // or knowing that source is in fact a map
        ProxyObject exposedObject = ProxyObject.fromMap(source);

this is what happens when the exposedObject is accessed in JavaScript:

exposedObject; // returns {complex: JavaObject[com.google.common.collect.SingletonImmutableBiMap], id: "1234567890"}

exposedObject.id; // returns 01234567890

exposedObject.complex; // returns {key=value}

exposedObject.complex.key; // returns undefined

So my question is how we can fully expose an arbitrarily complex and deep java object to javascript. Do we have to go through all members recursively and wrap them into ProxyObjects? Or is there a supported out-of-the-box method of achieving this?

Also, please let me know if my approach needs to change.

The_Outsider
  • 146
  • 8

1 Answers1

1

As the javadoc for ProxyObject [1] says "Interface to be implemented to mimic guest language objects that contain members.". This means that if you want the Java object to be used in JavaScript as if it was native to JavaScript it needs to be a ProxyObject.

On the other hand, as the website docs [2] show, Java objects passed into JavaScript can still be used as Java objects (i.e. they don't mimic JS objects by default). This means you can access fields, invoke methods, etc. The website docs show an example:

public static class MyClass {
    public int               id    = 42;
    public String            text  = "42";
    public int[]             arr   = new int[]{1, 42, 3};
    public Callable<Integer> ret42 = () -> 42;
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        context.getBindings("js").putMember("javaObj", new MyClass());
        boolean valid = context.eval("js",
               "    javaObj.id         == 42"          +
               " && javaObj.text       == '42'"        +
               " && javaObj.arr[1]     == 42"          +
               " && javaObj.ret42()    == 42")
           .asBoolean();
        assert valid == true;
    }
}

[1] https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/proxy/ProxyObject.html

[2] https://www.graalvm.org/reference-manual/embed-languages/

BoriS
  • 907
  • 5
  • 13
  • I agree that with allowAllAccess(true) this is possible. But the documentation advises against this when the code is not fully trusted. This will not work with explicit host access setting and objects whose members are only known at run time (are not decorated with HostAccess.Export), and objects with private members that are made public through annotations (Lombok). – The_Outsider Oct 09 '20 at 16:52
  • Sure, that's the tradeoff I guess. Open access to the host (java) types is rather risky. More security imposes more limits. Not sure if there is a way around it. – BoriS Oct 14 '20 at 11:44