In the web there's quite a lot of discussion about how to get JSON presentation of the domain objects using Grails and it's controllers. The part that seemed to be missing was how to be able to post JSON which has nested lists/elements to system and save those successfully.
But first, lets move a bit back from REST API to "normal" GSP pages + controllers combination. There was quite nice example of that at: http://www.objectpartners.com/2012/10/04/an-approach-to-processing-dynamic-one-to-many-forms-with-grails-2-1/. It's for a bit older Grails, but works with newer versions after changing plugin versions to valid ones. So, with that post we get working application, that's able to save parent object (Team), and it save list of child objects (Player) at the same time.
At this point it could be thought, that it's very easy to use JSON for both getting data from system and putting data to it. First part is actually very easy: Just one import and order Grails to render response as JSON:
When comparing Player objects (which clearly were the reason for this error), the reason came quite clear. The POST from GSP-page added "team" attribute to players :
Complete working example (for Grails 2.3.8) can be found at https://github.com/Hi-Fi/writetable-example-with-grails.
But first, lets move a bit back from REST API to "normal" GSP pages + controllers combination. There was quite nice example of that at: http://www.objectpartners.com/2012/10/04/an-approach-to-processing-dynamic-one-to-many-forms-with-grails-2-1/. It's for a bit older Grails, but works with newer versions after changing plugin versions to valid ones. So, with that post we get working application, that's able to save parent object (Team), and it save list of child objects (Player) at the same time.
At this point it could be thought, that it's very easy to use JSON for both getting data from system and putting data to it. First part is actually very easy: Just one import and order Grails to render response as JSON:
import grails.converters.JSONIf/when there's need for field selection or redering only part of the information, custom marshaller can be used. E.g. http://grails.org/plugin/marshallers.
...
render team as JSON (instead of redirect(action: 'show', params: [id:team.id]))
Saving data with JSON
When testing saving with just single objects (without nested properties) all worked like a charm. Grails can parse JSON directly to domain object, which can be just save. Problems arise when there's nested list. This kind of save just thows error about ids:
| Error 2014-12-20 19:20:16,127 [http-bio-8080-exec-9] ERROR errors.GrailsExceptionResolver - AssertionFailure occurredThis error comes to console/log, but saving of Team still succeeds with Hibernate 3. With Hibernate4 whole save is failed, which is kind of better solution.
when processing request: [POST] /junk/api/rest
null id in junk.Player entry (don't flush the Session after an exception occurs). Stacktrace follows:
Message: null id in junk.Player entry (don't flush the Session after an exception occurs)
Line | Method
->> 195 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
When comparing Player objects (which clearly were the reason for this error), the reason came quite clear. The POST from GSP-page added "team" attribute to players :
<junk.Player@1ba14c1b firstName=first lastName=last position=position deleted=false errors=grails.validation.ValidationErrors: 0 errors $changedProperties=null id=null version=null team=junk.Team : (unsaved)>while JSON POST left team as null:
junk.Player@76f8539e firstName=first lastName=last position=position deleted=false errors=grails.validation.ValidationErrors: 0 errors $changedProperties=null id=null version=null team=null>Solution for this was explicitly connect this new Team to all Players, which allowed save to make it's job:
team.players.each {it.team = team}
Complete working example (for Grails 2.3.8) can be found at https://github.com/Hi-Fi/writetable-example-with-grails.