How to eliminate cut and paste when using JSONBuilder

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

I have a Group domain object that contains the Members and Leaders sets. 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 tried to define the closure separately, but this leads to errors such as: exception: No such property: id for class:


Some notes:

1) Domain objects in the example are greatly simplified. I use JSONBuilder instead of render Group.list() as JSON or render Group.list().encodeAsJSON because I need to control which parts of my objects are encoded.

2) I will accept authoritative answers explaining why this is not possible.

+4
source share
2 answers

After repeated failures using closures, I have a solution. It does not use a closure directly, but instead uses a closure that returns a card.

 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 syntax m(person.toMap()) not intuitive, but it works and allows me not to repeat. This blog post details and explains the origin of the current Grails JSONBuilder.

+3
source

You can perform another closure in the same "context" by setting a "delegate" to close. A closure is not multithreaded (only one delegate at a time), so you will have to clone the closure each time if the closure is shared in a singleton class or static variable.

This is just an idea of ​​code refactoring, it may not work (I have not tested it). You can replace the assignment (=) with calls to the setProperty and DSL methods with "invokeMethod" if you need to dynamically determine the property name or method name in the DSL. (link 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') } } } } 
+1
source

Source: https://habr.com/ru/post/1339204/


All Articles