Rendering a Map to a Grails GSON View

skip intro

So, I am writing to write a Grails application when I discovered this new way to present JSON objects - using Grails GSON Views!

I haven't used Grails since version 2.x and now I jumped to version 5.1.1 – that's why GSON view is new to me!

GSON Basics

Basically, a GSON view works like a GSP view but we will write code using a JSON-like DSL instead of something HTML-like.

For example we have this controller:

class UserController {
    def show(Long id) {
	    respond new User(
            id: 1,
            firstName: 'Johnny',
            lastName: 'Appleseed',
            emailAddress: 'johnny_apples@example.com'
        )
    }
}

Then we have the corresponding GSON file in grails-app/views/user/show.gson:

model {
    User user
}

json {
    id user.id
    fullName "${user.firstName} ${user.lastName}"
    emailAddress user.emailAddress
}

With that, calling the endpoint will result a JSON that looks like the one in the GSON file.

{
    "id": 1,
    "fullName": "Johnny Appleseed",
    "emailAddress": "johnny_apples@example.com"
}

Grails views has a great documentation here and it shows different ways to render different objects using GSON. The problem here is I can't find in the documentation on how to properly render a Map. The goal here is to show the URL mappings of my Grails application dynamically.

{
    "message": "Welcome to my API!",
    "version": "0.1-dev",
    "urlMappings": {
    	"/": {
            "*": "index"
        },
        "/users": {
            "GET": "showAll",
            "POST": "save"
        },
        "/users/(*)": {
            "GET": "show",
            "PUT": "update",
            "DELETE": "delete"
        }
    }
}
This is how I want the URL mappings be shown in the endpoint I am making.

All I can see in the documentation is how to render a List, Set, or any linear collections in GSON.

json(1,2,3) == "[1,2,3]"
json { name "Bob" } == '{"name":"Bob"}'
json([1,2,3]) { n it } == '[{"n":1},{"n":2},{"n":3}]'
Example usages from the official documentation.

First, I tried  call(Iterable, Closure) method with the given Map as the 1st parameter. This did not work or even compile since Map is not an Iterable, according to the docs.

json {
    urlMappings urlMappingData, {
        // ... mapping logic ...
    }
}
First attempt on presenting a Map object using GSON. The Grails app did not start due to compilation error.

Second, I tried using the .entrySet() and .keySet() methods of the Map interface to make it fit with the Iterable requirement.

json {
    urlMappings urlMappingData.entrySet(), { entry ->
        def url = entry.key
        def mappingsList = entry.value
        // ... mapping logic ...
    }
}
Second attempt on presenting a Map object using GSON. Using this call(Iterable, Closure) method just maps the collection to a list.

This made the compilation errors disappear, but I still didn't get the JSON format I wanted to show. The Map should be presented with key-value pairs from the values stored in it and not create a List from a Map!

Create a new Map

While thinking of a solution, I remembered how we do JSON marshalling back in Grails 2: create a Map in the form of the JSON we want to present.

To do that, I used Groovy's collection manipulation methods such as .dropWhile(), .groupBy(), and especially .collectEntries() – which creates a Map from the size-2 (key, value) list that its closure returns. Now the GSON file looks like this:

model {
    UrlMappingsHolder urlMappingsHolder
}

json {
    message 'Welcome to my API!'
    version '0.1-dev'

    // creating the Map...
    def urlMappingData = urlMappingsHolder.urlMappings // get URL mappings
        .dropWhile { it.controllerName == null }       // show only controller endpoints
        .groupBy { it.urlData.urlPattern }             // group with same URLs
        .collectEntries { key, value ->  [
            key,                                      // key is still the URL
            value.collectEntries {
                [(it.httpMethod): it.actionName]      // method-action as the key-value pair
            }
        ]}

    urlMappings urlMappingData                        // present the created map in JSON
}
The show.gson file that builds the urlMappingData map in it and then showing it in a JSON field.

With the GSON file above, I can now show the URL mapping data with the format that I want it to be!

{
    "message": "Welcome to my API!",
    "version": "0.1-dev",
	"urlMappings": {
    	"/": {
            "*": "index"
        },
        "/users": {
            "GET": "showAll",
            "POST": "save"
        },
        "/users/(*)": {
            "GET": "show",
            "PUT": "update",
            "DELETE": "delete"
        }
    }
}

I hope this helps anyone that needs to render a Map to their Grails GSON view!