0

I'm writing a Grail REST Service and I've defined custom JSON converters. For instance, I have an event class which is converted like so whenever such an object is requested by a client...

// in src/groovy/EventMarshaller.groovy
class EventMarshaller {

void register(Object config) {
    config.registerObjectMarshaller(Event) { Event e ->
        return [
            id: e.id,
            title: e.title,
            description: e.description,
            dateCreated: e.dateCreated.format('yyyy-MM-dd'),
            creator: e.creator
        ]
    }
}

I register the EventMarshaller within a CustomObjectMarshaller which expects a named config parameter so that different REST API versions can be accommodated...

// in src/groovy/CustomObjectMarshallers.groovy
def register(String str) {
    JSON.createNamedConfig(str) { cfg ->
        marshallers.each {
            it.register(cfg);
        }
    }
}

// in BootStrap.groovy init section...
def springContext = WebApplicationContextUtils.getWebApplicationContext( servletContext );
    springContext.getBean("customObjectMarshallers").register("v1");

This works like a charm whenever I call to GET an event object via the REST API, the domain object is converted to the specified JSON format. For example, my event controller has an index action...

def index(String v) 
{
    def configName = 'v' + (v ?: 1)

    def listOfEvents = Event.list()

    // why does this only work when converting domain object to json?
    JSON.use(configName) {                  
        respond listOfEvents
    }
}

Now I need update and save actions when PUT and POST commands are received from a client. So far I have the following in my update action...

def update(Long id, String v) 
{
    def configName = 'v' + (v ?: 1)

    // how do I specify the version? JSON.use(configName) doesn't seem to work?
    def jsonobj = JSON.parse(request);
    def newEvent = new Event(jsonobj);
    def evRequest = new EventRequest(jsonobj)       

    evRequest.errors.allErrors.each {
        println it
    }
    ...

Can anyone explain how I can tell the update action which config to use when converting the JSON to a domain object? (I've not seen any example of this online at all.) Also, the dateCreated field in the newEvent object is null after the JSON is parsed. How can I ensure that if a dateCreated field is present that it will be parsed into it's original date object?

Here's the command object referenced in the code...

/**------------------------------------------------------------
// EVENT REQUEST COMMAND OBJECT 
//----------------------------------------------------------**/
class EventRequest
{
    String id;
    String title;
    String description;
    byte[] photo;
    @BindingFormat('yyyy-MM-dd')
    Date dateCreated;

    //------------------------------------------------------------ 
    // IMPORT CONTRAINTS FROM EVENT DOMAIN MODEL
    //------------------------------------------------------------
    static constraints = {
        importFrom Event;
    }
}

And the event domain class that it maps onto...

import java.util.Date;
import org.emvigr.user.*
import org.grails.databinding.BindingFormat;

class Event {

    String title
    String description
    byte[] photo
    @BindingFormat('yyyy-MM-dd')
    Date dateCreated

    // we can call user.addToEvents
    static belongsTo = [ 
        creator : User 
    ]

    static hasMany = [
        portals : Portal
    ]

    static constraints = {
        title maxSize: 50
        description nullable: true, maxSize: 300
        photo nullable: true, maxSize: 2 * 1024 * 1024
        dateCreated nullable: true
        portals nullable: true
    }

    // when Events are accessed sort by the dateCreated (descending)
    static mapping = {
        sort dateCreated:"desc"
    }

}

Any help much appreciated!

CSharp
  • 1,396
  • 1
  • 18
  • 41
  • 1
    You seem to be confusing yourself. You have custom converter, which will take a domain instance and create a JSON representation of it. Not the other way around. It has no impact whatsoever on data binding. When you accept some data and try and create a domain instance that's data binding not marshaling. They are two different concepts. – Joshua Moore Jun 19 '15 at 14:44
  • How then can we write the update action to deal with potentially numerous different versions of the json representation? – CSharp Jun 19 '15 at 15:01
  • 1
    You'll have to devise your own way of doing so if you want to, wrongly, use a single update method to handle differing formats. Typically you would create different controllers in different namespaces (v1, v2, v3, etc.) and implement them individually. This is common when versoning an API for instance. I think the Grails documentation even talks about using namespaces to version your APIs. – Joshua Moore Jun 19 '15 at 15:24
  • Thanks for the advice, I'll look into it. Do you have a link to any code as an example? (preferably showing how to deal with date conversion). – CSharp Jun 19 '15 at 15:48
  • 1
    Here is a code example showing the use of namespaces http://mrhaki.blogspot.com/2013/11/grails-goodness-namespace-support-for.html and as far as using multiple formats, I would just use command objects as your targets to bind to in the respective controllers. Again, all of this information is covered in the offical Grails documentation (search for namespace and command object). – Joshua Moore Jun 19 '15 at 15:52
  • Thanks again. I tried using a command object, currently it throws an exception because it can't convert the date from its string format in the json, but your advice is definitely helpful. Cheers! – CSharp Jun 19 '15 at 16:06
  • Use [`BindingFormat`](http://grails.github.io/grails-doc/2.5.x/api/org/grails/databinding/BindingFormat.html) – dmahapatro Jun 19 '15 at 16:08
  • Thanks @dmahapatro but it still doesn't work.. "Cannot cast object '2015-06-19' with class 'java.lang.String' to class 'java.util.Date'". Perhaps because the string is within a Json object rather than a parameter??? – CSharp Jun 19 '15 at 16:38
  • Add the command object to the question, showcasing use of `@BindingFormat` – dmahapatro Jun 19 '15 at 16:58
  • Added the command and domain object, see above... – CSharp Jun 19 '15 at 17:35

1 Answers1

0

Thanks Joshua Moore for the information on namespaces and dmahapatro re date binding. I was also having problems parsing a date parameter in my update which I now realise was because of the way I was (mis)using a command object.

For anyone experiencing the same problem, this may be particular to 2.4.4, but a class is only considered to be a command object when it is used as a parameter of an action according to official docs. Once I amended the update action like so...

def update(EventRequestCommand cmd) 
{               
    cmd.errors.allErrors.each {
        println it
    }

    if (!cmd.hasErrors()) {
        def ev = Event.get(cmd.id);
        // save changes
    }
    else {
        // handle error
    }
}

The date could somehow be parsed. I still don't know why it doesn't work if you create a class of EventRequest like in my original code above and I've also seen an example where a command object is not a parameter. I would of thought a date could still be parsed but I guess it's another confusing aspects of Grails.

Community
  • 1
  • 1
CSharp
  • 1,396
  • 1
  • 18
  • 41