开发者

How to eliminate cut-and-paste closures when using JSONBuilder

Does anyone know of a way to avoid repeating a closure when emitting the same object more than once using the latest version of the Grails JSONBuilder?

I have a Group domain object that contains sets of Members and Leaders. I would like to find a way to emit a person without having to cut and paste the closure.

    def builder = new JSONBuilder() 
    def result = builder.build {
        array {
            Group.list().each { Group group ->
                g = {
                    id = group.id
                    name = group.name

                    members = array {
                        group.members?.person?.sort().each { Person person ->
                            m = {  // Person closure copy #1
                                id = person.id
                                firstName = person.firstName
                                lastName = person.lastName
                            }
                        }
                    }

                    leaders = array {
                        group.leaders?.person?.sort().each { Person person ->
                            l = {   // Person closure copy #2
                                id = person.id
                                firstName = person.firstName
                                lastName = person.lastName
                            }
                        }
                    }
                }
            }
        }
    }

I have tried defining the closure separately but that leads to errors such as: exception: No such property: id for class:.


Some notes:

1) The domain objects in the example are greatly simplified. I'm using JSONBuilder instead of render Group.list() as JSON or render Group.list().encodeAsJS开发者_C百科ON because I need to control what parts of my objects are encoded.

2) I'll accept authoritative answers that explain why this can't be done.


After repeated failures using closures, I have a solution. It doesn't use a closure directly but instead uses a closure that returns a map.

class Person { 
    ...

    def toMap = {
        def map = [:]
        map["id"] = this.id
        map["firstName"] = this.firstName
        map["lastName"] = this.lastName
        return map
    }
}

def builder = new JSONBuilder() 
def result = builder.build {
    array {
        Group.list().each { Group group ->
            g = {
                id = group.id
                name = group.name

                members = array {
                    group.members?.person?.sort().each { Person person -> 
                        m(person.toMap())
                    }
                }

                leaders = array {
                    group.leaders?.person?.sort().each { Person person ->
                        l(person.toMap())
                    }
                }
            }
        }
    }
}

The m(person.toMap()) syntax isn't intuitive but it works and allows me to avoid repeating myself. This blog entry provides the details and explains the origin of the current Grails JSONBuilder.


You can execute another closure in the same "context" by setting the "delegate" for the closure. Closures aren't multi-thread safe (only one delegate at a time) so you will have to clone the closure each time if the closures are shared in a singleton class or static variable.

This is just an idea of refactoring the code, it might not work (I didn't test it). You can replace assignment (=) with "setProperty" and DSL method calls with "invokeMethod" if you have to dynamicly decide the property name or method name in the DSL. (reference http://groovy.codehaus.org/api/groovy/lang/Closure.html)

def personClosure = { Person person, varname ->
    setProperty(varname, {
        id = person.id
        firstName = person.firstName
        lastName = person.lastName
    })
}

def groupMembersClosure = { memberList, memberListVarName, memberVarName ->
    personClosure.delegate = delegate
    setProperty(memberListVarName, array {
        memberList?.person?.sort().each personClosure, memberVarName
    })
}

def builder = new JSONBuilder()
def result = builder.build {
    array {
        Group.list().each { Group group ->
            g = {
                id = group.id
                name = group.name

                groupMembersClosure.delegate = delegate

                groupMembersClosure(group.members, 'members', 'm')
                groupMembersClosure(group.leaders, 'leaders', 'l')
            }
        }
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜