Search code examples
jsongrails

Grails JSON Marshaller Shows a Different Date value than Original Date


In my Grails app, the original date read from the database is equal to:

{ endDate=2015-10-19 19:00:00.0}

While the JSON result is:

{"endDate": "2015-10-19T16:00:00Z"}

I think this is maybe related to time zone conversion. How could I show the original date without any timezone conversions in JSON?


Solution

  • Depending on which time zone you're in, 2015-10-19 19:00:00.0 and 2015-10-19T16:00:00Z may not be different times, they may be just different representations of the same time (instant).

    In my case, I use a custom marshaller to ensure that times in my API's JSON response always use the UTC time zone. My custom marshaller looks like this:

    import org.springframework.stereotype.Component
    
    @Component
    class DateMarshaller implements CustomMarshaller {
    
        @Override
        def getSupportedTypes() {
            Date
        }
    
        @Override
        Closure getMarshaller() {
            { Date date ->
    
                TimeZone tz = TimeZone.getTimeZone('UTC')
                date?.format("yyyy-MM-dd'T'HH:mm:ss'Z'", tz)
            }
        }
    }
    

    Remember to register the package this marshaller is in for Spring bean scanning in Config.groovy. The interface it implements is:

    interface CustomMarshaller {
    
        /**
         * Indicates the type(s) of object that this marshaller supports
         * @return a {@link Class} or collection of {@link Class} 
         * if the marshaller supports multiple types
         */
        def getSupportedTypes()
    
        Closure getMarshaller()
    }
    

    Then I have a service that registers all my instances of CustomMarshaller for the relevant type(s):

    import grails.converters.JSON
    import org.springframework.context.ApplicationContext
    import org.springframework.context.ApplicationContextAware
    
    import javax.annotation.PostConstruct
    
    class MarshallerRegistrarService implements ApplicationContextAware {
    
        static transactional = false
    
        ApplicationContext applicationContext
    
        // a combination of eager bean initialization and @PostConstruct ensures that the marshallers are registered when
        // the app (or a test thereof) starts
        boolean lazyInit = false
    
        @PostConstruct
        void registerMarshallers() {
    
            Map<String, CustomMarshaller> marshallerBeans = applicationContext.getBeansOfType(CustomMarshaller)
    
            marshallerBeans.values().each { CustomMarshaller customMarshaller ->
    
                customMarshaller.supportedTypes.each { Class supportedType ->
                    JSON.registerObjectMarshaller supportedType, customMarshaller.marshaller
                }
            }
        }
    }
    

    This is a fairly involved solution, but In my case I'm using Grails 2.5.X. If I was using Grails 3.X I'd try to use JSON views instead.