Data binding problem in JSON with nested object array
- Dominant language
- Groovy
- Stars
- 2.9k
- Forks
- 975
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 92
Description
### Steps to Reproduce
1. Create a Grails app with the rest-api profile.
2. Create two classes with *hasMany* and *belongsTo* correspondingly.
```
package test.app
import grails.rest.Resource
@Resource(uri='/person')
class Person {
String name
Group group
static constraints = {
group nullable: true
}
}
package test.app
import grails.rest.Resource
@Resource(uri='/group')
class Group {
String name
static hasMany = [people: Person]
static constraints = {
people nullable: true
}
static mapping = {
table '`group`'
}
}
```
3. Add and remove associations between those classes.
```
package test.app
import grails.plugin.json.builder.JsonOutput
import grails.plugins.rest.client.RestBuilder
import grails.plugins.rest.client.RestResponse
import grails.testing.mixin.integration.Integration
import groovy.json.JsonSlurper
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Stepwise
@Stepwise
@Integration
class ReplaceAssociationSpec extends Specification {
@Shared
RestBuilder rest = new RestBuilder()
@Shared
JsonSlurper jsonSlurper = new JsonSlurper()
private String url(String path) {
"http://localhost:${serverPort}${path}"
}
def "Verify a group can be saved"() {
when:
RestResponse resp = rest.get(url("/group"))
then:
resp.status == 200
resp.json == []
when:
resp = rest.post(url("/group")) {
json JsonOutput.toJson([name:"Test group 1"])
}
then:
resp.status == 201
when:
resp = rest.get(url("/group"))
then:
resp.status == 200
resp.json.size() == 1
}
def "Verify two persons can be saved"() {
when:
RestResponse resp = rest.get(url("/person"))
then:
resp.status == 200
resp.json == []
when:
resp = rest.post(url("/person")) {
json JsonOutput.toJson([name:"Person A"])
}
then:
resp.status == 201
when:
resp = rest.get(url("/person"))
then:
resp.status == 200
resp.json.size() == 1
when:
resp = rest.post(url("/person")) {
json JsonOutput.toJson([name:"Person B"])
}
then:
resp.status == 201
when:
resp = rest.get(url("/person"))
then:
resp.status == 200
resp.json.size() == 2
}
def "verify multiple persons can be associated to a group"() {
given:
RestResponse resp = rest.get(url("/person"))
List personIds = resp.json*.id
resp = rest.get(url("/group"))
Long groupId = resp.json.first().id
expect:
personIds.size() == 2
groupId != null
println JsonOutput.toJson([people: personIds])
when:
resp = rest.put(url("/group/$groupId")) {
contentType("application/json")
json JsonOutput.toJson([
people: personIds.collect { [id: it] }
])
}
then:
resp.status == 200
when:
resp = rest.get(url("/group"))
List personsAssociatedToGroup = resp.json.first().people*.id
then:
personsAssociatedToGroup.size() == 2
}
def "verify persons associated in a group can be replaced with a collection of less size"() {
given:
RestResponse resp = rest.get(url("/person"))
List personIds = resp.json*.id
resp = rest.get(url("/group"))
Long groupId = resp.json.first().id
expect:
personIds.size() == 2
groupId != null
when:
resp = rest.put(url("/group/$groupId")) {
json JsonOutput.toJson([
people: [personIds.first()].collect { [id: it] }
])
}
then:
resp.status == 200
when:
resp = rest.get(url("/group"))
List personsAssociatedToGroup = resp.json.first().people*.id
then:
personsAssociatedToGroup.size() == 1 // FAILS. Still 2 persons
}
}
```
### Expected Behaviour
The associations should be updated according to the type of operation (add or delete).
### Actual Behaviour
The binding works as expected when new associations are added but when I try to remove one or several associations the binding process fails. `cascade: 'all-delete-orphan'` isn't a solution because I don't want to delete the child. I only need to remove the association between the parent and the child. In the sample app the child class doesn't have a *belongsTo* but it has a reference to the parent class as a regular attribute (I tried with *belongsTo* and it doesn't work either). I tried to override the *getObjectToBind()* method but it's unnecessary because the body of the request has the collection assigned correctly.
### Environment Information
- **Operating System**: Arch Linux x86-64 (4.16.9-1-ARCH)
- **Grails Version:** 3.3.8
- **JDK Version:** 1.8.0_152
- **Container Version (If Applicable):** N/A
### Example Application
[Sample app](https://github.com/richardvil/test-app) with CURL commands to check the problem.
### Related issue
[#10909](https://github.com/grails/grails-core/issues/10909)
[Old discussion](https://groups.google.com/forum/#!topic/grails-dev-discuss/8Xl7OLdrqB0) with the same subject.
Contributor guide
Assessment
This issue has not been assessed yet.