Serializing/Deserializing Json in GWT
JSON & GWT
There was recently an interesting discussion in GWT Users group about best practices for serializing/deserializing JSON in the client side. This post aims to highlight its important points. There is so far three different ways of converting an object to JSON and back from the client side in GWT:
gwt-jackson framework:
gwt-jackson wraps some of the functionalities of the famous Jackson Library. It allows converting an object using an interface called ObjectMapper. The advantage of using gwt-jackson is that it takes care of serializing complex Objects such as Collections and Maps. It also allows fancy conversion using annotations such as @JsonProperty, and @JsonCreator. The only bummer of gwt-jackson is that it uses Generators which will be deprecated in the version 3.0 of GWT. It’s also worth noting that gwt-jackson is used by RestyGWT which is one of alternatives for making HTTP Requests from the client side.
Examples can be found in the Github Page: https://github.com/nmorel/gwt-jackson/tree/master/examples
using JavaScriptObject:
This is one of the traditional methods. JavaScriptObject makes use of JSNI to create a plain JS object. It can be extended and has a cast() method that allows “safe” casting the object to its sub-types. A JavaScriptObject can be converted to a JSON String using JsonUtils.stringify() method, and can be converted back using JsonUtils.safeEval(). JavaScriptObject works in conjunction with JsArray which represents a collection of JavaScriptObject, and extends JavaScriptObject. The only disadvantage of this method is the boilerplate associated with creating objects. For example:
public class Person extends JavaScriptObject { //Constructor needs to be protected and zero-arguments protected Person() { } // JSNI is used for setting/getting properties public final native String getFirstName() /*-{ return this.FirstName; }-*/; public final native String getLastName() /*-{ return this.LastName; }-*/; public final native void setFirstName(String firstName) /*-{ this.FirstName = firstName; }-*/; public final native void setLastName(String lastName) /*-{ this.LastName = lastName; }-*/; }
Then:
Person person = JavaScriptObject.createObject().cast(); JsArray array = JavaScriptObject.createArray().cast(); person.setFirstName("first Name"); person.setLastName("last Name"); array.push(person); GWT.log(JsonUtils.stringify(person)); GWT.log(JsonUtils.stringify(array));
Result:
{"FirstName":"first Name","LastName":"last Name"} [{"FirstName":"first Name","LastName":"last Name"}]
using JsInterop annotations:
JsInterop annotations allow treating a java type/class as a Javascript object, and exporting or importing functionalities to/from the application Js environment. Using JsInterop is the recommended method by some of the GWT project members (Thomas Broyer, Jens Nehlmeier), because JsInterop is an important part of the future of GWT, and it will be the main way of handling Javascript Objects from GWT. The only shortcoming for using JsInterop is that Elemental 2 is still in experimental phase, so until it gets stable. Developers are better off usng their own snippets for native Javascript utilities such as the Json class, for example:
@JsType(isNative=true, namespace=GLOBAL) public class JSON { public native static String stringify(Object obj); public native static Object parse(String obj); }
if our object looks like:
@JsType(isNative=true, namespace=GLOBAL, name="Object") public class Record { String id; String date; String data; public Record() { } }
Then :
Record record = new Record(); record.id = "1"; record.date = "20"; record.data = "30"; String json = JSON.stringify(recod); GWT.log(json); // Result: {"id":"1","date":"20","data":"30"}
JsInterop is used by autorest-gwt, which is also one of the options for making HTTP calls, to serialize/serialize objects prior to making HTTP requests.
It is important to note that Maps are not handled by JSON.stringify() method, an attempt to stringify a Map throws the following error:
Map mapTest = new HashMap(); mapTest.put("v1", "v2"); mapTest.put("v3", "v4"); GWT.log(JSON.stringify(mapTest));
Uncaught TypeError: Converting circular structure to JSON
Converting a Collection such as an ArrayList does not throw any error, but creates additional JSON fields that the developper would want to get rid of:
List test2 = new ArrayList(); test2.add(record2); test2.add(record); GWT.log(JSON.stringify(test2));
Result:
{"array_3_g$":[{"id":"1","date":"50","data":"90"},{"id":"1","date":"20","data":"30"}]}
The “array_3_g$” is added by GWT compiler for some reason, so the user needs to find a way to remove it for a clean conversion like:
{[{"id":"1","date":"50","data":"90"},{"id":"1","date":"20","data":"30"}]}
Plain Arrays is the only structure that is converted properly, so far.
Take Away
Out of the three methods, JsInterop seems the most viable method for handling JSON. The main concern for now is handling objects such as Collections and Maps which require some further manual processing by the developer. Plain Arrays is the only structure that is converted properly for now, so developers can try to simplify or convert to Plain Arrays for a clean conversion.
While JavaScriptObject offers its own way of dealing with “lists” using JsArray. gwt-jackson remains the only option now that offers the conversion of Collections and Maps out of the box.
Reference: | Serializing/Deserializing Json in GWT from our JCG partner Zakaria Amine at the G-Widgets blog. |