dynamphorous
dynamphorous

Reputation: 769

Stringify JSON from in GWT ArrayList then return to array

Currently attempting to stringify a Java ArrayList object in GWT using an interop method to call the native JSON.stringify(ArrayListobj). This results in a perfect JSON representation of the underlying array list contents in the form of an array. This even works for seemingly more complicated Java class objects, but in this instance I'm going to use Strings to demonstrate. First on my creation side:

test = new ArrayList<>();
test.add("first");
test.add("second");
String jsonstr = JSON.stringify(test);

Now based on my JsInterop code, I'm returning an Object (or object array as is the case in the following code shown here):

@JsType(isNative=true, namespace=GLOBAL)
public class JSON {
  public native static String stringify(Object obj);
  public native static Object[] parse(String obj);
}

So far so good, all of this works perfectly, and the results I get are stringified JSON representation of the contents of the ArrayList

"{"array_0":["first","second"]}"

Then run this bit through the parser:

ArrayList<String> returned = new ArrayList<>();
Object[] var = JSON.parse(jsonstr);

And var IS a proper reprsentation (when looking at the web browsers execution paused) of the underlying data from the first ArrayList. The problem is getting the Object[] of the JSON array converted back to the Java ArrayList object.

I have tried using JSNI code to extract the array elements, which actually in the console tray on the web browser works perfectly, but the compliler tries to outsmart me and renames the array element so my JSNI code can't touch it.

If my code is as above, and I write JSNI something like:

    public static native ArrayList objToList(ArrayList add,Object inVal) /*-{
         var length = inVal.array.length;
         for(var i = 0;i < length; i++){
             add.array[i] = inVal.array[i];
         }
         return add;
     }-*/;

Then the compiler will rename the array array_0 so my code that says inVal.array no longer ties in with the data I'm trying to access.

From all the testing I've done this is by far the fastest method of getting the same ArrayList object (its guaranteed to be defined the same way in both places) from one place in the client software to another place in client software (no server involved here) through stringification.

But the information about how to manipulate the JavaScript on a low level in GWT is, lacking at best.

And I've tried every variation on GWT-RPC mechanisms, GWT-Jackson, AutoBeans (if only they supported objects with multiple primitive types!) requestbuilder, you name it.

And NO, before you suggest it I'm not interested in doing a full GWT-JSON parse of the string again, I already did that when originally pulling the thousands of records off the server and pushed them into the Java ArrayList. It takes 200+mS to parse the JSON in GWT, while the browsers JSON parse function processes this string in around 3mS.

Upvotes: 1

Views: 1513

Answers (1)

Ignacio Baca
Ignacio Baca

Reputation: 1578

GWT uses type-markers to keep track of types and be able to do class cast safety. You must never use stringify with Java classes because you will lose those type-markers and also you will be using internal minimized/encoded symbols. So, this is how GWT internally handles all those types:

List<String> list = new ArrayList<>();
list.add("a"); list.add("b");
console.log("list", list);
console.log("array", list.toArray());
console.log("stringify", Global.JSON.stringify(list.toArray()));
console.log("parse", Global.JSON.parse(Global.JSON.stringify(list.toArray())));

console ouput The 'list' contains the obfuscated variable array_8_g$, this might change so you never ever should use this kind of encoding. The array result is ok, but you should notice that it contains various properties (typemarker, casteableTypeMap and __clazz), those extra properties are used to make java casting works, but are not enumerable so are not included in the next result stringify. This stringify result can be parsed-back as a String[], but now the result at parse have not included tye type-markers properties. So, if you immediately save the result of parse in a String[] variable, it will work correctly. But if you cast it as Object and try to cast-it-back to String[] it will fail.

In the jsinterop:base dependency there are 2 utilities Js#cast and Js#uncheckedCast that are helpful in these cases. If the array has a type-marker you can use Js#cast (this utility is the same as standard java casting), if not you must use Js#uncheckedCast.

In this example, the first line will succeed, and the second fails with a class cast exception:

console.log("uncheck", Js.<String[]>uncheckedCast(JSON.parse(JSON.stringify(list.toArray())))[0]);
console.log("check", Js.<String[]>cast(JSON.parse(JSON.stringify(list.toArray())))[0]);

You really should try to avoid mixing native JS code with Java. If you need to do that, then you must understand the internals of how GWT handle types, you must understand JSNI but use it as less as possible and finally understand how JsInterop works, and use it to access to native JS code or expose Java code to the JS world.

Upvotes: 3

Related Questions