Friday, May 27, 2011

A Day with Autobeans (or, JSON vs XML for data representation in GWT, Part 2)

I spent much of the day yesterday tinkering with AutoBeans, a technology that I stumbled across while trying to find a solution to the problems described in yesterday's post. (Thanks, StackOverflow!) The tagline on the AutoBeans describes exactly what I desire: structure, not boilerplate. The problem I always have with XML is that it takes so much plumbing to get the data into the domain model. Keep in mind that I am going to have content experts creating the data files manually, and so ORM tools, Hibernate, etc. won't solve my problem.

Structure

AutoBeans provide a mechanism for automagically reading JSON data into domain objects. I put together a functionally-equivalent demo akin to the XML demo. Specifically, I have a data file that contains the definition for an image and a "hotspot," a region of the image that generates a message when moused over. The data file looks like this:

{
    'image':'Jellyfish.jpg',
    'hotspots': [
        {
            'bounds': { 'left':10, 'right':50, 'top':10, 'bottom':50 },
             'message': 'You found it!'
        }
    ]
}

As before, this file sits right alongside my client source code and is referenced in a clientBundle, to wit:

public interface JsonResources extends ClientBundle {
    
    static final JsonResources INSTANCE = GWT.create(JsonResources.class);

    @Source("demo.json")
    TextResource place();
}

Setting up domain objects using AutoBeans is as simple as it should be: create interfaces using the traditional Java beans naming conventions. My three domain interfaces are shown below.


Compare these to the JSON above, and you can see that there's a strong parallelism: they are essentially two representations of the same semantic model.

The Hotspot and Place are "simple" interfaces, using the nomenclature of AutoBeans. They only contain bean-style accessor and mutator methods, and so they can immediately be deserialized from JSON. The Rectangle class is not simple since it has the contains method. The AutoBean documentation describes how to deal with this: create a "category" that provides an implementation of the interface's instance method as a static method with an extra parameter, as demonstrated below.

public class RectangleCategory {
    public static boolean contains(AutoBean<Rectangle> instance, int x, int y) {
        Rectangle r = instance.as();
        return r.getLeft() <= x //
                && r.getRight() >= x //
                && r.getTop() <= y //
                && r.getBottom() >= y;
    }
}


The last piece of the architecture is the AutoBean factory that wires Rectangle objects into their category. The critical piece for Rectangle is the @Category annotation.


@Category(RectangleCategory.class)
public interface MyAutoBeanFactory extends AutoBeanFactory {
    AutoBean<Rectangle> rectangle();

    AutoBean<Place> place();

    AutoBean<Hotspot> hotspot();
}

This completes the model and serialization framework. The only other critical piece is the UI, which is done with a custom Composite that uses constructor injection to receive its model, a Place. Hence, the whole system is contained in the following class diagram.


I modified my EntryPoint class to use this rather than my SimpleMessage approach, and it is short and sweet:

public class JSTest implements EntryPoint {

    private MyAutoBeanFactory factory = GWT.create(MyAutoBeanFactory.class);

    @Override
    public void onModuleLoad() {
        AutoBean<Place> placeBean = AutoBeanCodex.decode(factory, Place.class,
                JsonResources.INSTANCE.place().getText());
        PlaceWidget widget = new PlaceWidget(placeBean.as());
        RootPanel.get().add(widget);
    }
}


Analysis

One of the problems of dealing with GWT is that there can be a lot of silent errors. There's a sense in which mixing statically- and dynamically-typed code gives you the worst of both worlds. Then again, it might be my own blinders from dealing almost exclusively with statically-typed systems. For example, when I first wrote out the JSON, I called the bounding area 'rect', but when I wrote the domain object, I decided to be more explicit with my semantics and called the accessor getBounds. The AutoBeans system happily chugged along and gave me no warning that I was trying to access a field that was not there, because Javascript doesn't care about that.

The more aggravating error arose from a misconception about JSON. I was in a situation where my rectangles were being loaded, but with the wrong values: (0,0,0,0) instead of my JSON-specified values. I set it aside and played some Witcher 2, which you should buy DRM-free from the good people at gog.com, and then I decided to try writing unit tests to capture the error. The test is very simple, but I'll post it here for the sake of discussion:


public class RectangleTest extends GWTTestCase {

    @Override
    public String getModuleName() {
        return "com.example.archaeology_adventures";
    }

    public void testDeserialize() {
        MyAutoBeanFactory factory = GWT.create(MyAutoBeanFactory.class);
        
        AutoBean<Rectangle> rectBean = AutoBeanCodex.decode(factory,
                Rectangle.class,
                "{'left':10,'right':11,'top':12,'bottom':13}");
        Rectangle r = rectBean.as();
        assertNotNull(r);
        assertEquals(10, r.getLeft());
        assertEquals(11, r.getRight());
        assertEquals(12, r.getTop());
        assertEquals(13, r.getBottom());
    }

}

This test passes, but origially I was specifying the JSON literal thus:

"{'left':'10','right':'11','top':'12','bottom':'13'}"

I thought that all JSON objects were string:string pairs, just because that was my recollection from last Summer's Wave API experimentation. It turns out that if you deserialize that second JSON literal using AutoBeans, it will interpret strings as having the numeric value zero, and hence, give rectangle (0,0,0,0). Again, because this is dynamically typed, there is no warning or error about this conversion: you, the developer, just have to know that this is part of the language semantics. I assumed that there would be some kind of implicit conversion that doesn't happen. Removing the erroneous apostrophes solved this unit test's problem, and removing them from my experimental code fixed the problem there as well.

Conclusions

I am pleased that this technology provides a nigh-seamless way to go from a data representation to domain objects. Because there is little boilerplate code, it should permit rapid iteration much more than hand-parsed XML. It's possible that there's an analogous tool for XML of course, and if you know of one, please share it.

Coming back to the original problem, these data files will be created by non-programmer content-experts, although they will be collocated with the development team. Hence, one of the outstanding problems in the JSON/XML consideration is which of the following is more sensible to non-programmers:

{
    'image':'Jellyfish.jpg',
    'hotspots': [
        {
            'bounds': { 'left':10, 'right':50, 'top':10, 'bottom':50 },
             'message': 'You found it!'
        }
    ]
}

or

<place>
    <img src="Jellyfish.jpg"/>
    <hotspot>
        <rect x1='10' y1='10' x2='50' y2='50'/>
        <text>You found it!</text>
    </hotspot>
</place>

As always, I welcome your comments.

5 comments:

  1. nice article on autobean, so with this approach we can create json request at client side(GWT side) and when request submitted, json get binds to server side java pojo, so that we can access the submitted data via java beans, right or am I missing something?? Thanks for nice article again!

    ReplyDelete
    Replies
    1. I didn't do any networked transactions with the JSON; I was doing it all client-side, using JSON to make a DSL. What you describe sounds like it should work, though.

      Delete
  2. Say, is there any way to feed XML to an AutoBean? I'm working with a JSON API that is really just JSON serialized to XML output. But there are a few places where the API will only return XML. I love how easy and effective JSON has been with Autobean, but would love to pass those few XML requests through the same AutoBeans. Know of any tricks?

    ReplyDelete
    Replies
    1. Sorry, I don't know any other AutoBean tricks. My team ended up having to go with a non-Java solution due to other constraints, and I haven't tinkered with AutoBean for almost a year now. Good luck, though!

      Delete
  3. This comment has been removed by the author.

    ReplyDelete