Introducing Maprest: A Grails Plugin for Customized and Dynamic REST Services

Out of the box, Grails comes with some nice converters for XML and JSON, allowing your REST services to easily render in either of these formats. But these have their limitations. Take, for example, a Family class that consists of arrays of parents and children, both of which consists of Persons, and an Address.

class Family {
    Address address
    def parents = []
    def children = []
}

class Person {
    String firstName
    String lastName
    String socialSecurityNumber
    Integer age
}

class Address {
    String line1
    String line2
    String city
    String state
    String zipCode
    String type
}

Using the default renderer like this:

render getFamily() as XML

We get output like this:

<family>
	<address>
		<city>Minneapolis</city>
		<class>com.jondejong.maprest.demo.Address</class>
		<line1>123 Awesomesauce Drive</line1>
		<line2/>
		<state>Minnesota</state>
		<type>home</type>
		<zipCode>55401</zipCode>
	</address>
	<children>
		<person>
			<age>14</age>
			<class>com.jondejong.maprest.demo.Person</class>
			<firstName>Jamie</firstName>
			<lastName>Johnson</lastName>
			<socialSecurityNumber>111-22-3333</socialSecurityNumber>
		</person>
		<person>
		</person>
	</children>
	<class>com.jondejong.maprest.demo.Family</class>
	<parents>
		<person>
		</person>
	</parents>
</family>

I’ve simplified this response to only show one instance of each class. But what if you wanted to customize this output? At a minimum, we probably want to remove the class elements. Maybe we want to show the social security number only to specific requests. Maybe the child element to parents should be “parent”, not “person”. The standard way is to register your own custom object marshallers in the bootstrap. This can be very cumbersome. This is where the maprest plugin comes in.

Maprest

Maprest works by creating an element map from each object in the tree that makes up your response. It then uses that map to render either JSON or XML responses. You control the output by modifying how the element map is created. Maprest is available here on GitHub. Most of its features are shown in the maprest demo project, also on GitHub. This is the project I will walk through in this post.

With maprest, you implement the transformToMap() method on each class in the object tree that makes up your response. In future versions, this method will be optional and a default will rely on some smart assumptions. But for now, it is required. For example, in the Address class, we may have this:

public transformToMap() {
        [
                type: type,
                line1: line1,
                line2: line2,
                city: city,
                state: state,
                zipCode: zipCode
        ]
    }

That’s it. In our controller action, we can call the new render method available to us once the maprest plugin is installed:

renderMaprest(address, xmlFormat, 'address')

This method takes the object to be rendered (in this case, address), the format we want it rendered in, and the name of the root element. There are two formats available to you, xmlFormat, and jsonFormat. The root element is optional. If the format is xmlFormat, and the root element name is not supplied, the root element will simply be called “root”.

Now, the address will output like this:

<address>
	<type>home</type>
	<line1>123 Awesomesauce Drive</line1>
	<line2/>
	<city>Minneapolis</city>
	<state>Minnesota</state>
	<zipCode>55401</zipCode>
</address>

If we were to make this call:

renderMaprest(address, jsonFormat)

The response would look like this:

{
    "city": "Minneapolis",
    "line1": "123 Awesomesauce Drive",
    "line2": null,
    "state": "Minnesota",
    "type": "home",
    "zipCode": "55401"
}

All we’ve really done here is remove the class element. There’s so much more we can do.

Attributes

What if we wanted the type to be an attribute, not a child element, of the address. We could do this simply by marking the key in the Map with the ‘@’ symbol like this:

public transformToMap() {
    [
            '@type': type,
            ....
    ]
}

Note: attribute markings are ignored if the render method is set to jsonFormat. Now our output will look more like this:

<address type="home">
	<line1>123 Awesomesauce Drive</line1>
	<line2/>
	<city>Minneapolis</city>
	<state>Minnesota</state>
	<zipCode>55401</zipCode>
</address>

Child Objects and Collections

Let’s take a look at the Family class from the demo. Here things get interesting.

class Family {
    Address address
    def parents = []
    def children = []

    public transformToMap() {
        return [
                address: address.transformToMap(),
                parents: parents ? parents.collect { it.transformToMap() } : [],
                children: children ? children.collect { it.transformToMap() }.each {it.put('elementName', 'child')}: []
        ]
    }
}

Let’s walk through this, starting with the address. Here we create a key in our map called “address”, where the map representing the address is the value. If we wanted a String representation of the the address, we could have made the value address.toString() (or just the address instance as the value will be toString() by default). Now let’s take a look at the parents collection. Here we call collect() on the parents collection to create a new collection. Each item in the new collection is the map returned from the transformToMap() call on each element of the parents collection on the family instance.

The children collection is fun. To explain what’s going on here, I first have to explain how maprest handles sub-elements of collections. The parents element is easy. Each sub-element is called “parent”. By default, maprest will strip the last letter off of the collection name, assuming it to be an ‘s’. For the sub-elements of children, this does not work. We will end up with a bunch of elements called “childre”. This is obviously not what we want. Because of this, an element can always have a special key in it’s map called “elementName”. If a map has a key called “elementName” in it, maprest will use this for the name of the element. In this case, each sub-element of the children element will be called “child”.

Dynamic Maps

As an interesting side effect of using the transformToMap() method, it is very simple to dynamically create your element map at run time. In the demo, I’ve used a very contrived example in the Person class:

class Person {
    Boolean adminRequest
    String firstName
    String lastName
    String socialSecurityNumber
    Integer age

    Person() {
        adminRequest = false
    }

    public transformToMap() {
        def propertyMap = [
                "@fullName": "${firstName} ${lastName}",
                firstName: firstName,
                lastName: lastName,
                age: age
        ]

        if (adminRequest) {
            propertyMap.put("socialSecurityNumber", socialSecurityNumber)
        }

        propertyMap
    }
}

Here we are hiding the Social Security Number of the person unless the requester has admin access. Again, this is obviously a very contrived example, but it shows how simple it is to change the XML/JSON output based on runtime conditions.

Future Work

Maprest is a work in progress. At the time of this posting, it is on version 0.1.0. I wrote it to cover all of my needs, and tried to think of as many other cases as I could. If you feel there is a JSON or XML output that you can not easily create using maprest, I would love to hear about it and get it into future versions. Of course, you can feel free to fork the project and submit a pull request as well. I hope, in future versions, to make the transformToMap() function optional and rely on some smart defaults.

One thought on “Introducing Maprest: A Grails Plugin for Customized and Dynamic REST Services

  1. sconnelly says:

    Awesome! I will be using this soon.

  2. aeischeid says:

    sweet, been thinking about working on something like this , but never got around to it, it is really a nice feature to have your api outputs defined in your domain class instead of having marshallers spread in bootstrap or services

  3. Markus says:

    My biggest issues with REST is the input from the client.

    Being able to accept a JSON payload in a controller and create/update the needed objects in a graph.

  4. jdejong says:

    Markus,

    Have you tried telling Grails to parse the request object? In UrlMappings.groovy, something like:

    “/$controller/$action?/$id?”(parseRequest: true) {
    constraints {

    }
    }

    This will parse the JSON that the client posts/puts to you into the params Map on the Controller.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*