-
Notifications
You must be signed in to change notification settings - Fork 192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
allow custom converters from and to java #94
Comments
Thanks for the suggestion! Yes Nashorn exposed that through the dynalink API. That is the equivalent to exposing the full Truffle API to the embedder in terms of complexity. I agree we should have a mechanisms like this, but hopefully much simpler. The challenge will be to not sacrifice performance for this (if not used and if used only for the affected types). I'd suggest to add following:
This is slightly different to your suggestion. I am not sure what we could provide a reasonable fromType Class. Also, what would that be useful for? @pmlopes Would that fit your use-cases? |
@chumer this would be great as a user I could map my own js types to java types before interop! This would cover all the current use cases I have:
Plus allow users to register custom converters e.g.:
👍 |
I'm afraid that this increases the chance for ambiguities in the method overload selection, even if there's only one converter per type allowed. If there are multiple host converters involved, we can't decide which one's the best. Dynalink has a solution for this, called @pmlopes You don't need a source type guard, do you? |
@woess in my comment I was saying that matching would be on exact class (no hierarchy) to reduce the complexity and I believe that only allow only 1 converter per class is enough. Perhaps the API should return the previous converter if any or null. In this case I me as user really needs multiple converters, then I'd write the chain myself. WDYT? |
There is typically a single entity, naming the embedder, deciding conversion. A comparator is not needed, right? The embedder can add conversion comparator on top if necessary. |
If host converters accepted all source types, that would make things simpler, as you'd only need to look at the target (parameter) types to select method and converter. The downside is lack of flexiblity. Say you have two method overloads with different custom converters:
Of course, you could define a type ranking, but then one type always get chosen over the other. |
I think the example above also relates to another topic that has been discussed in gitter, say we have the following interface from the comment above: interface InterOpTest {
void method(JsonObject arg);
void method(Instant arg);
} When calling this form the host (graaljs) the method arity is the same so it is not obvious which method to call, for this we need something like we had on nashorn to explicitly select the desired method e.g.: // assume myInstanceOfInterOpTest is an instance of InterOpTest
myInstanceOfInterOpTest.method['java.time.Instant'](new Date()) With such a construct in place the choice of the method is not a issue, so we can now continue with the convertion which allows us to look just to the target type which is clearly |
Here is the new API that is work in progress. Feedback appreciated: public class Test {
public static class MyClass {
@HostAccess.Export
public void foo(JsonObject c) {
}
}
public static class JsonObject {
JsonObject(Value v) {
}
// omitted
}
public static void main(String[] args) {
HostAccess customMapping = HostAccess.newBuilder().//
allowAccessAnnotatedBy(HostAccess.Export.class).//
typeMapping(JsonObject.class,
(v) -> v.hasMembers() || v.hasArrayElements(),
(v) -> new JsonObject(v)).build();
try (Context c = Context.newBuilder().allowHostAccess(customMapping).build()) {
c.getBindings("js").putMember("javaObject", new MyClass());
c.eval("js", "javaObject.foo({})"); // works!
c.eval("js", "javaObject.foo([])"); // works!
c.eval("js", "javaObject.foo(42)"); // fails!
}
}
} Precedence will be determined by the order of the typeMapping specification. So the earlier declared mappings get precedence. Note that the standard type mappings except the Object.class type mapping (as this fits all values) always gets precedence. For example, a method with int signature that is passed a number that fits int will not use any mappings for int. @pmlopes your issue about the method overload selection is a separate issue we are working on and we hope to get to it soon. |
@chumer I think this is great, just one question, I assume we can we have multiple: typeMapping(JsonObject.class,
(v) -> v.hasMembers() || v.hasArrayElements(),
(v) -> new JsonObject(v)).build(); right? If the anwser is yes then 👍 |
@pmlopes Of course. As many as you like :-) |
@chumer, will the mappings also get applied to elements of Collections and Maps? For example:
|
@ispringer Yes. That is the plan. Will also work for arrays. Need more tests for that, good reminder. |
@chumer, thanks, that's great to hear. Do you plan on adding additional built-in conversions for JDK types that have obvious mappings? For example, the JavaScript Date class has a straightforward conversion to a Java Date or Instant class. It would be nice if that and similar cases "just worked". The below test summarizes the current lack of support for Date conversion:
|
@chumer This looks great! We're already doing our own makeshift type mapping for List and Map in Java-land. From "JavaScript Usage Examples" in http://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html#as-java.lang.Class- I would like way to configure the Context so that instead of:
We get: I'm thinking this could work
but I have the following questions:
Sorry for the long-winded questions and thanks in advance. |
@ispringer we are thinking about a few more standard mappings. Date/Instant is one of them. Currently, our focus is on getting the converters right and removing lossy conversions that we currently do for String -> number and number -> string. @johnwatts Thank you. These are excellent questions.
Yes you need to add them for all the base types manually. The type mappings work with exact target types only.
Yes. The type mappings work on any kind of recursive mapping. This includes fields, method parameters, proxy/functional interface return values and varargs. BTW.: your example in this form does not work. The CustomRequest class needs to be an interface to be used like you want it for Value.as(CustomRequest).
If you call type mapping recursively in a custom type mapping, then only standard mappings will be applied for those values. Do you see any problems with that? |
Here is the latest javadoc and method signature:
I've added a source type to simplify recursive mappings. So you can now use any source type including Value.class. |
We plan to remove some standard builtin coercions that we were not happy with. You will be able to reconfigure the mappings like this:
|
Thanks for the preliminary Javadoc. That prompts one new question:
What is the effect of returning null? Is it the same as a converter throwing an exception or returning false in the accept predicate? I think this would block me from converting a JS number to Java Integer while mapping JS NaN to Java null. And following up on my earlier questions:
Yup. Thanks for the reminder. I incompletely refactored my example plus I was suffering from a little bit of wishful thinking. Sorry.
The custom type mapping will have to take responsibility for type mapping all the nested sub-objects if I want it to apply recursively. My overall goal is to be able to accept arbitrary JS objects (typically constructed from JSON). The reason it doesn't work currently is because JS arrays have members so they become Maps, not Lists if their target type isn't sufficiently constrained. I would like to be able to fix that with a custom type mapping
Which will work for this example
But because Value.as doesn't apply custom typemapping it won't work for
My choices are either to use Value.as and not have my custom type mapping apply recursively or walk the object graph myself which leads to a lot more code in the typemapper that is basically reimplementing the standard typemapping behavior (probably poorly). I need that decision (JS [] -> Java List) to apply recursively at all levels but that is the only behavior I want this typemapper to be concerned with. I can see why you might not want Value.as to invoke custom typemapping but could there be a way to explicitly delegate the creation of nested objects to full typemapping instead of the standard typemapping only? Another example where this might matter is that you said you plan to "remove some standard builtin coercions". If someone puts those standard coercions in a custom typemappings, I would again think that a custom typemapper should at least have the option to call an API similar to Value.as and have those previously standard coercions apply rather than having to reimplement that logic for subfields inside one or more custom typemappers. |
@johnwatts
Null values are now supported. Here is your full example:
This prints on tip:
This feature will be part of RC 15. Closing. |
@johnwatts Thinking again. Actually, this version should be a tiny bit faster than my previous suggestion:
|
Am I right in assuming this can't be used with the NodeJS interop, as there's no context access? |
When working with Java interoperability, basic conversions happen between types that cross across languages. A js object becomes a java Map for example.
There are cases where it would be interesting to allow user defined conversions. For example:
Having a interface that could be:
In order to keep performance acceptable this functions could be stored on a identity hash map and only one converter per specific type allowed.
Plus matching should be the exact class so avoid to much reflection or runtime type information.
The example above is just for illustration, an alternative could be looking at nashorn dynamic linker API that allows amongst other things this exact scenario.
You can see a simple usage here:
https://github.com/reactiverse/es4x/blob/develop/es4x/src/main/java-9/io/reactiverse/es4x/dynalink/ES4XJSONLinker.java
The text was updated successfully, but these errors were encountered: