LiquidPlayer / LiquidCore

Node.js virtual machine for Android and iOS
MIT License
1.02k stars 128 forks source link

JSValue.tojavaObject(Object.class) should return Java representation #53

Closed consp1racy closed 5 years ago

consp1racy commented 6 years ago

There are three points to this.

Object.class

JSValue.tojavaObject(Object.class) should return whatever that object would be in Java instead of this (of type JSValue).

This would allow pure Java bridge in the sense I'll try to describe here.

Sample

I'll be trying to pass the following object as a function argument from JS to Java:

{
    "some":"thing",
    "hell":0.0,
    "what":["is","this"],
    "this":{"is":"sparta"}
}

The Java function defines parameter of type Map<String, Object>.

Current behavior

Currently the resulting argument received by Java function could be described as Map<String, JSValue>, because everything coming from JS is a JSValue. Only the top-most element is resolved as Map<String, Object>.

What I get (ordering is not guaranteed, that's fine for now):

mapOf(
    "hell" => JSValue,
    "some" => JSValue,
    "this" => JSValue,
    "what" => JSValue
)

Expected behavior

What I want is to completely abstract JavaScript from the Java function, unless the parameter is defined as JSValue or its children.

Wanted result:

mapOf(
    "hell" => 0,
    "some" => "thing",
    "this" => mapOf("is" => "sparta"),
    "what" => listOf("is", "this")
)

The same result would even be passed if the function parameter type was Object.

String.class

Second case would be conversion to String. If Java function expects a string best effort should be made to convert whatever comes from JS to string. That means

Detached results

Resulting objects should be detached from any original real JS objects. E.g.

return new HashMap<>(new JSObjectPropertiesMap(o, Object.class));

HashMap constructor resolves the dynamic map to actual stable values before passing that to the Java function.

Future enhancements

Ability to plug in Moshi so it resolves Map<String, Object> or JSON string to arbitrary Java object types automatically.

Motivation

I'm trying to replicate and expand the same API that WebView offers for registering JS to Java bridge using addJavascriptInterface. I want to hide any JS* objects from consumer. That also means a function handler should not need to extend JSObject or JSFunction.

I'm working on this and will file a separate issue/pull request.

(This part is now over my head so I'm using ByteBuddy to generate child classes of JSObject at runtime that delegate method calls to whatever class the consumer supplied.

Now what

I already have a working demo of the ideas described here (minus automated tests). Is any of this something that goes along with the goals of your library and you'd like to see merged?

ericwlange commented 6 years ago

Thanks for the detailed proposal. Sure, this is something that would be quite useful. I have never been completely happy with the implementation of toJavaObject(), but it worked for my limited purposes. As it happens, I have a nearly working version of LiquidCore for iOS. It is running Node.js on top of JavaScriptCore (rather than V8), and JSC object conversion to Objective-C and Swift already works as you describe. So it would be nice to have symmetry between the platforms.

One thing, though, if you haven't already managed this. You will probably need to add a test for handling circular values:

var a = {};
var b = { a : a };
a.b = b;

Passing either a or b to Java should not result in an endless loop.

consp1racy commented 6 years ago

Oh right, that's a good point. How did you solve it on the iOS side?

I seem to recall there's a list of all known JSObjects somewhere. So then I'd need to maintain a Map<JSValue, Object> of resolved JS valeus during the top-most toJavaObject call.

ericwlange commented 6 years ago

I didn't solve it. It comes out of the box with JavaScriptCore. I'm not sure what it does with circular references. I will have to devise a test.

However, this did come up when I was building out the micro service implementation. JSON.stringify() is not safe from circularity. See MicroService.java:279.

consp1racy commented 6 years ago

Awesome, so for toJavaObject(String.class) I should be able to leverage safeStringify. For Object.class I'll have to figure something out.

Also, another good catch, support for JSONArray and JSONObject as Java function parameter types.

consp1racy commented 6 years ago

According to this stringify throws an error if it encounters a circular reference.

Personally I'm okay with that happening in the Java bridge. At least in the toJavaObject(String.class) method.

toJavaObject(Object.class) could ~be bent to either support circular references or~ throw as well.

How much do you insist on safeStringify? For me it makes little sense to support circular references in any form across the bridge.

ericwlange commented 5 years ago

Closing due to lack of activity and demand.