apache / apache/grails-core

Data binding problem in JSON with nested object array

Open
#11,061 0 comments 0 reactions 1 assignee Claimed by @niravassar View on GitHub
status: awaiting validation
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.