Grails Tip: Getting JSON directly from Grails MongoDB-GORM Low Level API

One of the nice things about MongoDB is that documents are returned in JSON format.

If you use the Mongo-GORM plugin, however, the driver conveniently converts the returned results into a Grails domain class instance. You then have to serialize this domain class to get a JSON result back.

In this post, I will show how to use the low level mongodb API to just get a JSON response back in your controllers.

Fetching a Single Document

In a typical application, we would do the following:

def show(String name){
      def pageInstance = Page.findByName( name )
      if( pageInstance ){
          render pageInstance as JSON
      } else {
          render status: SC_NOT_FOUND
      }
}

If you ran this command, you would be surprised to see that the JSON that you get back is very different that the one you would get directly with the driver.

In particular, the default mode of the JSON encoder doesn’t return child elements, so you have to do some extra work to get that working.

Also, it seems terribly inefficient to get an object from JSON and then having to convert it back.

Instead, we can use the low level API and just get the document as a DBObject. The text representation of this value will be the JSON format returned by the database.

We change Page.findByName( name ) into Page.collection.findOne(name:name) and simply return it as a text value.

def show(String name) {
      def pageInstance = Page.collection.findOne(name:name)
      if (pageInstance) {
          render contentType: 'application/json', text: pageInstance
      } else {
          render status: SC_NOT_FOUND
      }
}

One niggle with this approach is that the low level api is not supported in Grails unit tests, so you would have to mock out the collection property within your Domain Class in tests to do what you need. A neat trick here shown to me by Rob Fletcher is to simply map to the corresponding dynamic finder in Grails. So in your tests, you would have something like:

Page.metaClass.static.getCollection = {->     
     [findOne: {
          delegate.findWhere(it) as JSON
     }]
}

Fetching Many Documents

This approach breaks catastrophically when the you’re trying to apply it to something that returns a list of objects. This is because the underlying Java Driver for Mongo will return a DBCursor that allows you to traverse the results instead of the entire document.

Let’s say we have a method in our domain class that does the following:

def list(String type) {
     render Page.findAllByType(type) as JSON
}

The equivalent low level API call is as follows:

def list(String type) {
    def cursor = Page.collection.find(type: type)
    def items = []
    try {
        while (cursor.hasNext()) {
            items << com.mongodb.util.JSON.serialize(cursor.next())
        }
    } finally {
        cursor.close()
    }
    render contentType: 'application/json', text: '[' + items.join(',') + ']'
}

Here, we are using the JSON serializer that ships with the MongoDB driver instead of the one provided by Grails. The results are much closer to the documents returned by Mongo.

This does make unit testing more complex, as you need to then mock out all the grails bits for cursor traversal. Here is an example from one of our tests:

given:
    DBCursor cursor = Mock(DBCursor)
    DBObject show1 = Mock(DBObject)
    DBObject show2 = Mock(DBObject)
    com.mongodb.util.JSON.metaClass.static.serialize = { item -> item.name }
    Page.metaClass.static.getCollection = {->
        [find: { cursor }]
    }

when:
    controller.list('show')

then:
    with(cursor){
        hasNext() >> true >> true >> false
        next() >> show1 >> show2
        1 * show1.get('name') >> 'show1'
        1 * show2.get('name') >> 'show2'
        1 * close()
    }

and:
    response.text == '[show1,show2]'

You can simplify this beautiful little clusterfuck by wrapping the low level call into a service and then just mocking out the service method within your tests and then testing the service in a manner similar to the one above.

2 thoughts on “Grails Tip: Getting JSON directly from Grails MongoDB-GORM Low Level API

  1. Pingback: An Army of Solipsists » Blog Archive » This Week in Grails (2013-20)

Leave a comment