Grails REST API - Render JSON with paging, metadata and custom include fields from params
Asked Answered
S

2

13

I'm looking for a best way to design a custom JSON response for RESTful API in Grails based on this presentation Design Beautiful REST + JSON APIs by Les Hazlewood.

Here is my Domain class

class TaxiType {

    Date dateCreated, lastUpdated
    String description
    User createdBy

    static hasMany = [taxis: Taxi]

    static constraints = {
    }
}

The desired response format for list is

{
  "meta": {
    "href": "https://api.mydomain.com/taxi-types",
    "otherData": "..."
  },
  "paging": {
    "offset": 0,
    "limit": 10,
    "total": 100,
    "first": "https://api.mydomain.com/taxi-types?offset=0&limit=10",
    "previous": null,
    "next": "https://api.mydomain.com/taxi-types?offset=90&limit=10",
    "last": "https://api.mydomain.com/taxi-types?offset=90&limit=10"
  },
  "data": [
    {
      "href": "https://api.mydomain.com/taxi-types/1",
      "id": 1,
      "description": "description 1",
      "taxis": {
        "href": "https://api.mydomain.com/taxi-types/1/taxis"
      }
    },
    ...
  ]
}

TaxiTypeController.groovy

def index(Integer limit) {
    params.max = Math.min(limit ?  : 10, 100)
        params.offset = params ? .offset ? .toInteger()
        withFormat {
        json {
            respond TaxiType.list(params),
            [includes : includeFields,
                paging : [total : TaxiType.count(), limit : params ? .max, offset : params ? .offset ?  : 0]
            ]
        }
    }
}
private getIncludeFields() {
    params.fields ? .tokenize(', ')
}

SumoJsonCollectionRenderer.groovy

class SumoJsonCollectionRenderer extends JsonCollectionRenderer {
    SumoJsonCollectionRenderer(Class componentType) {
        super(componentType)
    }
    public SumoJsonCollectionRenderer(Class componentType, MimeType...mimeTypes) {
        super(componentType, mimeTypes)
    }
     @ CompileStatic(SKIP)
     @ Override
    protected void renderJson(object, RenderContext context) {
        log.debug(object)
        log.debug(object.size())
        log.debug(object.getTotalCount())
        Map tObject = ['data' : object]
        if (context.arguments ? .paging) {
            tObject['paging'] = context ? .arguments ? .paging
        }
        super.renderJson(tObject, context)
    }
}

Required features are:

1) API users should be able to get only the required fields (Partial representations)

GET https://api.mydomain.com/taxi-types??fields=id,description,taxis

the desired response for this request should be

{
    "meta" : {...}
    "paging" : {...}
    "data" : [{
            "href" : "https://api.mydomain.com/taxi-types/1",
            "id" : 1,
            "description" : "Taxi Type1",
            "taxis" : [{
                    "href" : "https://api.mydomain.com/taxis/123"
                },
                ...
            ]
        },
        ...
    ]
}

What I'm getting was

{
    "data" : [{
            "id" : 1,
            "description" : "Taxi Type1",
            "taxis" : [{
                    "class" : "com.domain.project.Taxi",
                    "id" : 1
                }
            ]
        },
        ...
    ],
    "paging" : {
        "total" : 80,
        "limit" : 10,
        "offset" : 0
    }
}

I referred answer to this question Render metadata for pagination from Grails RestfulController index/search actions.

But still need to include links for first, previous, next & last in paging as mentioned in the desired format above.

2) Customizing output

for example the taxis property with hasMany relationship should be rendered by default as

"taxis": {
    "href": "https://api.mydomain.com/taxis/12345"
}

and if the user prefer to expand taxis property, for eg: GET /taxi-types?expand=taxis, the JSON format should be

"taxis": {
    "href": "https://api.mydomain.com/taxis/12345"
    "name": "Taxi name",
    "type": "https://api.mydomain.com/taxi-types/1"
    ...
}

3) Add meta object to all responses

"meta": {
     "href": "https://api.mydomain.com/taxi-types",
     ...
}

I tried Json Marshaller to customize the response. Here is my Marshaller code

JSON.registerObjectMarshaller(TaxiType) {TaxiType taxiType->
    return [
        id : taxiType ? .id,
        description : taxiType ? .description
    ]
}

but it always renders with those two properties in return array id & description, even if I want some other properties like taxis.

How above 3 requirements can be implemented?. Thanks in advance.

Syne answered 29/10, 2014 at 16:15 Comment(2)
Here are some examples that may get you started: mrhaki.blogspot.com/2014/07/… Also see the "Related Posts" at the bottom of the page, mrhaki's articles and notebooks are an excellent resource for all things Groovy & Grails.Shipley
This question has a lot of useful knowledge itself ... i am using it as a response (!)Iberian
P
0

I've created a plugin for grails 3 which allows you to add pagination, custom metadata and other useful features to your apis. You can add HAL features using LinkGenerators as well with my plugin.

Predominant answered 22/4, 2016 at 12:47 Comment(1)
I'm noticing a lack of security (no tokens for your api calls), no CORS support, no api docs, no developer SDK, no support tools (benchmarking/profiling). Why is this better than the standard rest plugin that comes with Grails?Greengrocery
G
0

well the Beapi-API-Framework plugin is the #1 api plugin for grails and provides pagination by default. See the github repo for a complete list of all functionality, documentation and install instructions

Greengrocery answered 4/4, 2021 at 18:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.