GWT has a fantastic tool for "overlaying" java (GWT) classes on JSON objects such that you don't have to write any parsing code to parse a piece of JSON. There are some nice articles about this from the GWT folks themselves, for example this one. The gist of it is, given some JSON like this:

{ "item": {
    "id": 123,
    "name": "cheese",
    "type": "food"
  }
}

we can "overlay" a java class onto that JSON, and have the GWT compiler (and the browser's native JSON parsing) do the marshalling for us.

JavaScript Overlay Types

We do that by writing a JavaScript Overlay Type class that maps the native json data back to the the world of your Java code, and looks like this:

public class Item extends JavaScriptObject
{
    protected Item(){}

    public final native Integer getId()/*-{ 
        return this.id; 
    }-*/;

    public final native String getName()/*-{ 
        return this.name; 
    }-*/;

    public final native String getType()/*-{ 
        return this.id; 
    }-*/;
}

JSNI Methods

Those three native methods are known in GWT-land as JSNI methods (JavaScript Native Interface), and the part in comments at the end is actually pure JavaScript that you can write to directly access parts of the JSON object.

In these native JSNI sections the "this" object being referred to is the JSON object itself, and although I've only shown very simple property access here you can in fact perform more complex operations if you like.

The return values from our JSNI can be Java's primitive wrappers or String, any class that extends JavaScriptObject (JSO), or one of a number of special JSO classes GWT provides such as JsArray, JsIterable, JsArrayString, etc.

I have exclusively used jsni methods in my example above, but you can include pure java methods as well (provided they are final) - say, for example, we need to parse a date from some json:

public class Item extends JavaScriptObject
{
    private static final DateTimeFormat df = 
        DateTimeFormat.getFormat("yyyyMMdd");

    protected Item(){}

    public final Date whenCreated() {
        return df.parse(getCreateDateString());
    }

    public final native Integer getId()/*-{ 
        return this.id; 
    }-*/;

    public final native String getName()/*-{ 
        return this.name; 
    }-*/;

    public final native String getType()/*-{ 
        return this.id; 
    }-*/;

    public final native String getCreateDateString()/*-{ 
        return this.createDate; 
    }-*/;
}

Mapping complex types

These are simple examples of course, but i've had no trouble mapping even the most complex JSON objects using JSO's. You can map arrays easily enough using the built in GWT JsArray types, like this:

public class Items extends JavaScriptObject
{
    protected Items(){}

    public final native JsArray getItems()/*-{ 
        return this.items; 
    }-*/;
}

Which would map the following JSON:

{ items: [
  {
    "id": 123,
    "name": "cheese",
    "type": "food"
  },
  {
    "id": 456,
    "name": "ham",
    "type": "food"
  },
  {
    "id": 789,
    "name": "eggs",
    "type": "food"
  }]
}

JavaScriptObject rules

There are a couple of basic rules to follow:

  • Must have a protected no-arg constructor
  • All methods must be "final"
  • Must not have any instance fields
  • May only implement a single Interface

JavaScriptObject "gotchas"

With regard to that last point, there's another gotcha to look out for: only one JavaScriptObject class can implement any given interface. Think about that for a minute, because its more of a restriction than you might at first realise...

The whole point of Interfaces is to use them to define a contract that implementations will adhere to, so that multiple poly-morphically interchangeable implementations can be used. With JSO's, you can only have one single JSO implementation of an interface - for example you cannot have several JSO's which implement Iterable. This is for the same reason that all JSO methods must be final - JSO's cannot use dynamic method dispatch.

If you try to implement the same interface in more than one of your JSO's you don't (currently) get any warnings from the compiler ... instead you get crazy runtime behaviour due to the wrong JSO class's methods being invoked (because one of your JSO's will receive all method invocations for the interface, regardless of which class the method should have been invoked against).

Interestingly I ran into a problem in the latest GWT release (2.4) that reported a very unhelpful error message (I'm sure the messages in earlier releases of GWT were much more helpful actually). Here's the error message I got:

java.lang.ClassFormatError: Illegal method name "$"
 in class com/xxx/xxx/client/model/UserResult
 at java.lang.ClassLoader.defineClass1(Native Method)
 at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
 at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
 at java.lang.ClassLoader.defineClass(ClassLoader.java:465)
 at com.google.gwt.dev.shell.CompilingClassLoader.findClass
    (CompilingClassLoader.java:1085)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:247)

... as it turns out, the problem actually was that I broke one of the rules - I had a non-final method in my JavaScriptObject. Not a very helpful error message in this case, which is fairly unusual in my experience of GWT.

blog comments powered by Disqus