Grails XML marshalling: change default “” ro

2019-05-28 16:18发布

问题:

By default Grails renders List in XML with a <list> element tag at its root. Likewise it renders Map with <map>. I would like to control the name of the root element.

If I'm returning an ArrayList of User, then I'd like to see:

<users>
    <user>...</user>
    <user>...</user>
</users>

How can I achieve the above? Here are the Requirements:

  • Easy to apply this serialization for 50+ domain classes
  • Abstracted from developers so no explicit coding is required during rendering domain objects (i.e., when render() or respond() is invoked, an ArrayList is still passed in, no explicit casting/converting like as MyNewType)
  • Able to handle the edge case of an empty list (should return <users/>)

Nice-to-haves:

  • If this formula can be applied to Map as well, great :)

I have been semi-successful in achieving the goals above, except I don't know how to account for the empty list case. I implemented my own ObjectMarshaller which renders all objects of type List. So long as the list contains one element, I can check the element's type and determine what the plural tag name should be (User => users). But if the list is empty, and since Java generics are by erasure (unless that's different in Groovy?) then I have no way to properly name an empty list other than defaulting to something like <list/>, which is not acceptable.

Some resources that I've been through:

  • http://www.cacoethes.co.uk/blog/groovyandgrails/dry-json-and-xml-with-grails
  • http://grails.1312388.n4.nabble.com/Custom-XML-Marshaller-change-the-root-element-name-td4649949.html
  • http://jwicz.wordpress.com/2011/07/11/grails-custom-xml-marshaller/
  • http://mrhaki.blogspot.com/2013/11/grails-goodness-register-custom.html
  • http://manbuildswebsite.com/2010/02/15/rendering-json-in-grails-part-3-customise-your-json-with-object-marshallers/

回答1:

A way to achieve this is to write a subclass for the CollectionMarshaller class and register it in our Grails application. We can for example register a custom implementation in BootStrap.groovy with the following code:

import org.codehaus.groovy.grails.web.converters.marshaller.xml.CollectionMarshaller
import grails.converters.XML

class BootStrap {

  def init = { servletContext ->
    // Register custom collection marshaller for List with User instances.
    // The root element name is set to users.
    XML.registerObjectMarshaller(new CollectionMarshaller() {
        @Override
        public boolean supports(Object object) {
            object instanceof List<User>
        }

        @Override
        String getElementName(final Object o) {
            'users'
        }
    })
  }
}

To make this work for more domain classes we might get a reference to all domain classes in BootStrap.groovy and loop through them to configure custom CollectionMarshaller instances.

For maps you can extend MapMarshaller

Also described in http://mrhaki.blogspot.com/2014/02/grails-goodness-customize-root-element.html