0

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.

dynamphorous
  • 739
  • 1
  • 9
  • 20
  • Which GWT-JSON parse are you doing that takes 200mS? Or is that time taken when the iterating through all the elements and copying into the array? Also, what is the question you are asking in this question - you write a lot about what you have tried (and @ibaca describes the misunderstanding you have), but the question doesn't seem to make clear you you are trying to achieve - do you wish to serialize content so that GWT can read it again later as real GWT objects? or turn an ArrayList back into something you can send to the server? Or something else more specific? – Colin Alworth Feb 07 '19 at 16:11
  • Colin Alworth, I am still trying to achieve the same thing as my previous serialization / deserization question you answered. Trying to use the GWT-RPC was one of a myriad of approaches I have tried with this, but I was never able to get that working correctly as there was insufficient documentation, and I was mostly cutting and pasting (read: hacking up) code to try to make it work. This approach was one that made the most sense to me, as I have done exactly what Ignacio had done, and saw that I was 95% of the way there i already knew I needed the helpers in the array, so it was concatenation – dynamphorous Feb 07 '19 at 21:38
  • It might help to ask the actual question you have, rather than problems with the solution you're working on (though I'm happy to discuss internal details of the solutions too). I should note that I'm in the process of getting the RPC work I mentioned before in production for one of the apps I work with, so you can probably have some confidence in that approach (assuming that's the other question you're asking about - I don't tend to "stalk" people I answer questions for, so I lose track of which answer was given to who). – Colin Alworth Feb 07 '19 at 22:24
  • Understood, and I was not suggesting you were. I can clearly see you are a GWT expert, and specialize in RPC, so no shocker to me you hit on these two threads since its what I'm struggling with. All I want is a very light weight method of passing an already parsed ArrayList of objects consisting of (mostly) primitive types (so AutoBeans are out) to the main UI thread from a webworker. The methods I've run into so far, GWT-RPC & its asymmetric inconsistencies as you pointed out, GWT-jackson had lots of issues, and felt very bloated for my needs, AutoBeans doesn't support multiple object types.. – dynamphorous Feb 08 '19 at 00:27
  • AutoBeans can be used with some degree of polymorphism, but it is limited by JS (and maps _directly_ to it, as opposed to making up its own properties to work out types) - I have at least a few SO answers on the topic, will link later. If you want polymorphism from a GWT module to another one, you need some "artificial" mechanism to set up types, since even two different modules with the same classes might obfuscate differently (at least their typeIds will almost certainly be different). – Colin Alworth Feb 08 '19 at 00:32
  • If gwt-jackson didn't work for you, do also consider gwt-jackson-apt (also reachable on gitter) - the author is actively adding more jackson features, but will still be limited by what jackson can do for polymorphism (mostly @JsonSubTypes, currently being implemented I believe for the -apt project). – Colin Alworth Feb 08 '19 at 00:35
  • I found AutoBeans to be promising, but the documentation on it was EXTREMELY limited, and I didn't get it working correctly even with simple strings. The strings were always null. Do you know of any good tutorials on the subject? And GWT-Jackson never compiled correctly. I'm not a super expert, but I'm also not so green that I don't know how to compile a module with maven dependencies, and that one just gave me all the errors at once. I opened a ticket on it, but nobody has responded in two days: https://github.com/nmorel/gwt-jackson/issues/153 – dynamphorous Feb 08 '19 at 00:37
  • The GWT-jackson project may be dead, and uses the Generator API, which will not work in GWT 3+, so is generally discouraged. GWT-Jackson-APT is attempting to redesign the tool to work in past and future versions of GWT. I'll look for a simple AutoBean example later and link to it. – Colin Alworth Feb 08 '19 at 00:41

1 Answers1

2

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.

Ignacio Baca
  • 1,538
  • 14
  • 18
  • Ignacio, I agree with you completely about avoiding using Javascript code whenever possible. My issue was that I can examine what is going on internally with the javascript, but when trying to cast back to Java types once objects have been serialized / deseralized is much harder to understand what is actually happening internally. This whole project is for an internal company project, so I'm far less concerned about the issues of supporting this sort of hack in a larger commercial production environment. – dynamphorous Feb 07 '19 at 21:41
  • Also, you will note that I DID run the loop for putting the Javascript array data into the Java array. That was due to the type-marker elements that are in the ArrayList Java code, and not wanting to lose them through something akin to add.array = inval.array. That I agree would completely screw up the ArrayList java element. – dynamphorous Feb 07 '19 at 21:44
  • 1
    If you try to re-create "java objects" without actually calling the constructors (or getting deep into the internals of how gwt defines a type, and an instance of that type), you will risk losing track of some of those details. Others actually don't matter to copy. Remember too that JSON.parse doesn't really work even for plain JS objects much of the time, since it loses any instance methods or prototype inheritance. – Colin Alworth Feb 07 '19 at 22:23
  • Colin, I agree and understand what you are saying. What I was trying involved running the same constructor that I had called to create the original ArrayList (guaranteed to be the same since the webworker and the main thread share the same class) and I am just passing data from the webworker via string over to the main client application, then trying to reconstruct the same arraylist. The RPC mechanisms so far have been so excessively complex, plus the fact I could pause the execution, do the above operation, then let the code run and it would work perfectly gave me hope in this approach. – dynamphorous Feb 08 '19 at 00:17
  • Sharing the same "class" is only sufficient if they are actually running the same compiled module and permutation, compiled at the same time, otherwise details might be obfuscated differently or have different type IDs assigned. – Colin Alworth Feb 08 '19 at 00:36