JsInterop & collections

785 views
Skip to first unread message

Marcin Okraszewski

unread,
May 8, 2017, 9:21:29 AM5/8/17
to GWT Contributors
Hi,
Is there any progress in JsInterop support for collections? If not, is there any document that would explain how was it planned? We would really need it and potentially we might help in it.

Thanks,
Marcin

Jens

unread,
May 8, 2017, 10:09:59 AM5/8/17
to GWT Contributors
IMHO if you want a JavaScript array, set, map behave the same as a Java collection, so you can use it with other libraries, you should write an adapter class that implements the Java API and operates internally on the JavaScript data type. 

Basically do not rely on invisible magic. That could easily be implemented as a 3rd party project.

-- J.

Ray Cromwell

unread,
May 8, 2017, 10:42:17 AM5/8/17
to google-web-tool...@googlegroups.com
The adapter class adds overhead though and you need to convert into and out of it every time you pass it to JS. At that point you may as well use Java.util.List and write an adapter around JS array.


--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-web-toolkit-contributors/4d08f88c-ec88-4818-b778-3e0e247f2e7e%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Marcin Okraszewski

unread,
May 8, 2017, 4:27:59 PM5/8/17
to GWT Contributors
Basically what we need it for is REST. Currently we use AutoBeans, but we want to change it because we need to pass the model to JS too; secondly AutoBeans generate a lot of code. We would like to still preserve shared model with the server. So, JsInterop seems the most natural choice, except it doesn't support collections. We mostly need List and Map. Switching to JsInterop with replacing Lists with some JS array view would be quite an effort. It would introduce that into our server logic too (by the shared model). And and you could even use Java's for each loop, as native JsType cannot extend non-JsType interfaces, so it could not extend Iterable (of course adapter could be instantiated for every list you want to iterate). Same for streams. 

Therefore we look for collection support in JsInterop, which was planned for "phase 2". If that was in our reach, we could help in getting it.

Marcin


On Monday, 8 May 2017 16:42:17 UTC+2, Ray Cromwell wrote:
The adapter class adds overhead though and you need to convert into and out of it every time you pass it to JS. At that point you may as well use Java.util.List and write an adapter around JS array.

On Mon, May 8, 2017 at 7:10 AM Jens <jens.ne...@gmail.com> wrote:
IMHO if you want a JavaScript array, set, map behave the same as a Java collection, so you can use it with other libraries, you should write an adapter class that implements the Java API and operates internally on the JavaScript data type. 

Basically do not rely on invisible magic. That could easily be implemented as a 3rd party project.

-- J.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.

Manuel Carrasco Moñino

unread,
May 8, 2017, 4:44:40 PM5/8/17
to google-web-tool...@googlegroups.com
If in your model is enough to have Collections/Lists based on ArrayList you can easily convert from JsArrays to ArrayLists and vice-versa by using a bit of JSNI since the ArrayList implementation in GWT relies on a javascript array. 

Take a look to this code used in the gwt-polymer-elements library (put attention to asList and asArrayList methods)


- Manolo


On Mon, May 8, 2017 at 10:27 PM, Marcin Okraszewski <okr...@gmail.com> wrote:
Basically what we need it for is REST. Currently we use AutoBeans, but we want to change it because we need to pass the model to JS too; secondly AutoBeans generate a lot of code. We would like to still preserve shared model with the server. So, JsInterop seems the most natural choice, except it doesn't support collections. We mostly need List and Map. Switching to JsInterop with replacing Lists with some JS array view would be quite an effort. It would introduce that into our server logic too (by the shared model). And and you could even use Java's for each loop, as native JsType cannot extend non-JsType interfaces, so it could not extend Iterable (of course adapter could be instantiated for every list you want to iterate). Same for streams. 

Therefore we look for collection support in JsInterop, which was planned for "phase 2". If that was in our reach, we could help in getting it.

Marcin


On Monday, 8 May 2017 16:42:17 UTC+2, Ray Cromwell wrote:
The adapter class adds overhead though and you need to convert into and out of it every time you pass it to JS. At that point you may as well use Java.util.List and write an adapter around JS array.

On Mon, May 8, 2017 at 7:10 AM Jens <jens.ne...@gmail.com> wrote:
IMHO if you want a JavaScript array, set, map behave the same as a Java collection, so you can use it with other libraries, you should write an adapter class that implements the Java API and operates internally on the JavaScript data type. 

Basically do not rely on invisible magic. That could easily be implemented as a 3rd party project.

-- J.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsu...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.

Goktug Gokdogan

unread,
May 8, 2017, 6:17:00 PM5/8/17
to google-web-toolkit-contributors
What do you mean exactly by "collection support in JsInterop"? From you description I only understand that you want to keep using java collection API for existing & server side code.
If you want use your JavaScript array/map instance from Java, you need an adaptor; doesn't matter who provides. And sure you can do it yourself.
If you want use you Java collection from JavaScript, we already jsinterop-enabled many collection APIs.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.

Marcin Okraszewski

unread,
May 9, 2017, 6:08:12 AM5/9/17
to google-web-tool...@googlegroups.com
In short I would like JS arrays to be visible in GWT as a List and objects parsed from JSON also as a Map.

Maybe I'll try one more time to explain what we have. We share model between server and GWT in form of java interfaces. Those interfaces are declared as return types of Jersey services, which serve instances of those interfaces. GWT uses exactly the same interfaces and handles them with AutoBeans. It works quite well, but there are two issues. First issue is that occasionally we need to pass some part of the data to JS. More often than not, it is a list or contains a list. As a result, we need to repackage it, as GWT objects are not understood by JS. Now we plan to leverage more JS, so we will have to deal with this problem more often. Second issue is that AutoBeans turned out to be inefficient in larger scale, as the generated code is roughly 50% of our written code.

So, we came up with following idea: let’s keep shared interfaces, just annotate them with @JsType(native=true). Then use regular browser’s JSON parser and cast parsed object into model interface with @JsType. It works like charm for simple objects, but not for collections - List is being parsed as an JS array; Map is nothing else but just regular object. Dropping support for java collections, would be very expensive, and would require heavy refactoring, as the app is big (over 0.5 million Java lines in GWT + affected server parts). I thought there could be a way to tell JsInterop that specific field is backed by a plain array (or list) and an object (for map).

I believe it was in plans with @JsConvert – see slides 67 – 69. https://docs.google.com/file/d/0ByS1wxINeBWjeGYxbkJpamxFZ28/edit

Marcin


--
You received this message because you are subscribed to a topic in the Google Groups "GWT Contributors" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/google-web-toolkit-contributors/Be2m_AnyL_g/unsubscribe.
To unsubscribe from this group and all its topics, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-web-toolkit-contributors/CAN%3DyUA1BE9gw6700%3Dia5Lq-2kOPahesOcX9SGcQSFvKrbO_mtw%40mail.gmail.com.

Jens

unread,
May 9, 2017, 7:11:47 AM5/9/17
to GWT Contributors

I believe it was in plans with @JsConvert – see slides 67 – 69. https://docs.google.com/file/d/0ByS1wxINeBWjeGYxbkJpamxFZ28/edit

I think this is more meant to be used with non-native JsTypes. When you have a JsType implemented in Java and some JavaScript gives you JS through the Java object's public JS api. That way you can simply generate a delegate method that wraps the Js Array and provides the Java collection API on top of it.

However if you use native JsTypes together with JSON.parse() for convert JSON back to JavaScriptObjects you are only dealing with a naming convention and nothing more. Basically JSON.parse() sees your server generated JSON and will construct JS objects natively. Given that JSON.parse() only understands JS Objects, JsArrays and primitives the native JsType you will cast the result of JSON.parse() into, can also only have these types in order to access the underlying data. So if your server REST implementation serializes a Map as two JSON arrays (one for keys, one for values) then your native JsType also needs two JsArray fields in order to access them. The only thing you can do is define a @JsOverlay method that returns a Map implementation that wraps the two JsArrays and then use that @JsOverlay method in your code to get a Map view.

Given that you can only modify native JSON.parse() behavior to a limited extend I don't see any other way how a native JsType could transparently add an adapter around JsArrays in a configurable way (different REST server implementations might serialize maps in different ways) other then using @JsOverlay methods.


-- J.

Thomas Broyer

unread,
May 9, 2017, 7:30:40 AM5/9/17
to GWT Contributors

On Tuesday, May 9, 2017 at 12:08:12 PM UTC+2, Marcin Okraszewski wrote:
In short I would like JS arrays to be visible in GWT as a List and objects parsed from JSON also as a Map.

Maybe I'll try one more time to explain what we have. We share model between server and GWT in form of java interfaces. Those interfaces are declared as return types of Jersey services, which serve instances of those interfaces. GWT uses exactly the same interfaces and handles them with AutoBeans. It works quite well, but there are two issues. First issue is that occasionally we need to pass some part of the data to JS. More often than not, it is a list or contains a list. As a result, we need to repackage it, as GWT objects are not understood by JS. Now we plan to leverage more JS, so we will have to deal with this problem more often. Second issue is that AutoBeans turned out to be inefficient in larger scale, as the generated code is roughly 50% of our written code.

So, we came up with following idea: let’s keep shared interfaces, just annotate them with @JsType(native=true). Then use regular browser’s JSON parser and cast parsed object into model interface with @JsType. It works like charm for simple objects, but not for collections - List is being parsed as an JS array; Map is nothing else but just regular object. Dropping support for java collections, would be very expensive, and would require heavy refactoring, as the app is big (over 0.5 million Java lines in GWT + affected server parts). I thought there could be a way to tell JsInterop that specific field is backed by a plain array (or list) and an object (for map).

How about using the 'reviver' argument of JSON.parse() to copy/wrap arrays into lists? (would be harder to detect those objets that you want into maps though; the "reviver" could probably be generated from your interfaces to match on the key name though – this is something I've been thinking about for a few days actually, I really need to put my thoughts together in a gist or a message to this list)

Marcin Okraszewski

unread,
May 9, 2017, 10:34:48 AM5/9/17
to GWT Contributors
There is indeed something in it. Actually you could have some type of naming convention, like in TJSON (https://tonyarcieri.com/introducing-tjson-a-stricter-typed-form-of-json) or TypedJson (https://www.npmjs.com/package/typed-json) to figure out proper types. But then I would need to create eg. ArrayList with the Manuel's trick (the asList() from Polymer). I'll test it. 

Thomas Broyer

unread,
May 9, 2017, 12:32:33 PM5/9/17
to GWT Contributors


On Tuesday, May 9, 2017 at 4:34:48 PM UTC+2, Marcin Okraszewski wrote:
There is indeed something in it. Actually you could have some type of naming convention, like in TJSON (https://tonyarcieri.com/introducing-tjson-a-stricter-typed-form-of-json) or TypedJson (https://www.npmjs.com/package/typed-json) to figure out proper types. But then I would need to create eg. ArrayList with the Manuel's trick (the asList() from Polymer). I'll test it. 

java.util.Arrays.asList() should be enough actually: https://github.com/gwtproject/gwt/blob/2.8.1/user/super/com/google/gwt/emul/java/util/Arrays.java#L136 (note that the ArrayList there is not java.util.ArrayList, it's an internal java.util.Arrays.ArrayList class that directly wraps the array with no copy).

Goktug Gokdogan

unread,
May 9, 2017, 6:24:08 PM5/9/17
to google-web-toolkit-contributors
Yes, theoretically you should be able to use the second parameter on Json.parse Json.stringify for conversion back and forth between java collections and js primitives. In this model, your javascript code needs to use Java collection APIs.

> java.util.Arrays.asList() should be enough

keep in mind that Arrays.asList won't let you go out of bounds.



> I believe it was in plans with @JsConvert


We are not working on @JsConvert right now. JsConvert is just convenience and you can mimic it:


  @JsType(isNative=true)
  interface MyType {
     @JsConvert(ListConverter.class)
     List getMyArray()
     void setMyArray(@JsConvert(ListConverter.class) List array)
  }

is roughly equivalent to:

  @JsType(isNative=true)
  interface MyType {
     @JsProperty(name="myArray")
     Object[] getMyArrayInternal();

     @JsOverlay
     default List getMyArray() { return Arrays.asList(getMyArray()); }

     @JsProperty(name="myArray")
     void setMyArrayInternal(Object[] array);

     @JsOverlay
     default void setMyArray(List list) {
       setMyArrayInternal(array.toArray());
     }
  }


--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-web-toolkit-contributors/95e1bd43-b4a6-4b2d-bd89-6cc6fb206631%40googlegroups.com.

Ray Cromwell

unread,
May 9, 2017, 7:19:58 PM5/9/17
to google-web-toolkit-contributors
I think it would be better to use a JsArrayListAdapter in order to prevent making copies all over the place, and also making mutations write-through on both sides e.g.

public class JsArrayListAdapter<T> extends AbstractList<T> {
   public JsArrayListAdapter(ArrayLike<T> blah) {
      this.array = blah;
  }

  // implement List methods to delegate to Array methods
}

To keep referential integrity, you need to use an expando or ES6 Symbol property to hide a backreference.

That is, JsArrayListAdapter.wrap(nativeJsArray) == JsArrayListAdapter.wrap(nativeJsArray2) IFF nativeJsArray == nativeJsArray2.

This technique is less error prone IMHO if you are going to have mutable objects. If you're using immutables, then making copies is superior.


 


To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-web-toolkit-contributors/CAN%3DyUA0Wr0pWKUcqyN2YMkuoZZK7b3e-ipm7jqv2DdDeqk8sMA%40mail.gmail.com.

Marcin Okraszewski

unread,
May 10, 2017, 11:40:53 AM5/10/17
to GWT Contributors
I've done something that I think is so far the closest to what I want to achieve - the same object behaving as List in Java and as array in JS. Actually it can be seen in Java as either list or array - both work :-)

First I declare JsArray:

@JsType(isNative=true, name="Array", namespace=JsPackage.GLOBAL)
public class JsArray<E> {
  // JS method declarations here, but can be also empty
}

Then extend create a List that extends the JsArray

@JsType
public class JsArrayList<E> extends JsArray<E> implements List<E> {
  // List implementation here
}

Now, parse JSON and replace arrays with instances of JsArrayList.

private static native <T> T parse(String json) /*-{
return JSON.parse(json, function(key, value) {
if (Array.isArray(value)) {
var arrayList = new @interop.client.JsArrayList::new();
Array.prototype.push.apply(arrayList, value);
value = arrayList;
}
return value;
});
}-*/;

Now, if the JSON includes any array, you will be able to use in Java as List.

Problems so far:
  1. All elements of the array need to be copied. I was tempted to just change __proto__ with the one from JsArrayList, but that is said to affect performance. I could also add methods from JsArrayList to the instance, not sure if that is better from performance standpoint. 
  2. The extended array is not recognized as an array in JS (although it behaves as array). Array.isArray() returns false, same for instanceof Array. I guess, adding functions to array instance would solve it. 
  3. While parsing metadata are missing, what actual type should that be. Switching arrays to list is a decision without context. But when we want to treat an object as map, then it would need to be known somehow. It can be naming convention of fields, but it is not perfect; could not supported nested collections. Some meta provided from the interface that is being cased, would be great. 
Do you have any thoughts on that maybe?

Thank you,
Marcin
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.

sup...@bpilotglobal.com

unread,
May 10, 2017, 12:53:06 PM5/10/17
to GWT Contributors

I am still new to GWT (so this code is probably naive), but I implemented this JSON parse wrapper which performs "transparent" conversion of JS arrays to java.util.List and using replacer/reviver functions:

Could this be made more general?

Daniel Kurka

unread,
May 10, 2017, 2:02:46 PM5/10/17
to GWT Contributors
First of all do not use JSNI going forward.

Use elemental2 (or define your own JSON.parse):

Without elemental2:
@JsType(isNative=true, namespace = JsPackage.GLOBAL);
public class JSON {
  public static Object parse(String s, Reviver r);
}

@JsFunction
public interface Reviver {
  public Object run(String key, Object value);
}

// usage:
MyObject o = (MyObject) JSON.parse(s, (key, value) -> {
  // whatever change you need to do
  return value;
});

-Daniel

To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-web-toolkit-contributors/651f6423-853c-4c25-9b92-86d8ab711067%40googlegroups.com.

Marcin Okraszewski

unread,
May 10, 2017, 4:14:38 PM5/10/17
to GWT Contributors
Thanks for hint. Indeed, it is the same concept here. The only difference in parser is that I targeted in a List implementation that would still work as array in JS, so I can share exactly the same model between JS and GWT. I also plan to use JsInterop to access objects, while I see overlay types are used here, which would make it more difficult to share with server; but again conceptually is same.

Thanks you,
Marcin

Marcin Okraszewski

unread,
May 10, 2017, 4:22:40 PM5/10/17
to GWT Contributors
Looks better indeed. Are there also some other reasons not to use JSNI? Eg. performance, or it is going to be removed in future?

How would I call  Array.prototype.push.apply(arrayList, value); via JsInterop? Or if I wanted to add a function to an existing object?

Thank you,
Marcin
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-contributors+unsubscribe@googlegroups.com.

Daniel Kurka

unread,
May 10, 2017, 5:21:18 PM5/10/17
to GWT Contributors
In general we consider JSNI deprecated and it will not be present in the new J2CL transpiler.

JsInterop (together with Jsinterop base) is powerful enough to do all things you were doing in JS.

public class Helper {
  @JsMethod(name = "Array.prototype.push.apply", namespace=JsPackage.GLOBAL)
  static void apply(Object that, Object value);
}


To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "GWT Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-web-toolkit-contributors/dfe8b1da-5547-4fb4-812b-73c8f14257a3%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages