-
Notifications
You must be signed in to change notification settings - Fork 38.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Nested record containing a query param of type List not being resolved correctly #32379
Comments
Actually It failed if param name contains index (auto-growing) and no corresponding setter method, no matter it's nested or not, no matter it's record or not. import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.core.ResolvableType;
import org.springframework.validation.DataBinder;
import org.springframework.web.bind.WebDataBinder;
import static org.assertj.core.api.Assertions.assertThat;
class WebDataBindTests {
@Test
void nested() {
Map<String, String> data = new HashMap<>();
data.put("pojo.list[0]", "test");
data.put("record.list[0]", "test");
data.put("pojoWithoutSetter.list[0]", "test");
WebDataBinder binder = new WebDataBinder(null, "test");
binder.setTargetType(ResolvableType.forClass(Holder.class));
binder.construct(new DataBinder.ValueResolver() {
@Override
public Object resolveValue(String name, Class<?> type) {
return data.get(name);
}
@Override
public Set<String> getNames() {
return data.keySet();
}
});
binder.bind(new MutablePropertyValues(data));
Holder holder = (Holder) binder.getTarget();
assertThat(holder.pojo.getList()).first().isEqualTo("test"); // works
assertThat(holder.record.list()).first().isEqualTo("test"); // failed
assertThat(holder.pojoWithoutSetter.getList()).first().isEqualTo("test"); // failed
}
@Test
void topLevelRecord() {
Map<String, String> data = new HashMap<>();
data.put("list[0]", "test");
WebDataBinder binder = new WebDataBinder(null, "test");
binder.setTargetType(ResolvableType.forClass(Record.class));
binder.construct(new DataBinder.ValueResolver() {
@Override
public Object resolveValue(String name, Class<?> type) {
return data.get(name);
}
@Override
public Set<String> getNames() {
return data.keySet();
}
});
binder.bind(new MutablePropertyValues(data));
Record record = (Record) binder.getTarget();
assertThat(record.list()).first().isEqualTo("test");
}
@Test
void topLevelPojoWithoutSetter() {
Map<String, String> data = new HashMap<>();
data.put("list[0]", "test");
WebDataBinder binder = new WebDataBinder(null, "test");
binder.setTargetType(ResolvableType.forClass(PojoWithoutSetter.class));
binder.construct(new DataBinder.ValueResolver() {
@Override
public Object resolveValue(String name, Class<?> type) {
return data.get(name);
}
@Override
public Set<String> getNames() {
return data.keySet();
}
});
binder.bind(new MutablePropertyValues(data));
PojoWithoutSetter pojoWithoutSetter = (PojoWithoutSetter) binder.getTarget();
assertThat(pojoWithoutSetter.getList()).first().isEqualTo("test");
}
record Holder(Pojo pojo, Record record, PojoWithoutSetter pojoWithoutSetter) {
}
record Record(List<String> list) {
}
static class Pojo {
private List<String> list;
public List<String> getList() {
return this.list;
}
public void setList(List<String> list) {
this.list = list;
}
}
static class PojoWithoutSetter {
private final List<String> list;
PojoWithoutSetter(List<String> list) {
this.list = list;
}
public List<String> getList() {
return this.list;
}
}
} |
@quaff Thanks for the info! So are you saying it can't possibly work with records because of their immutability? |
The POJO is mutable so every parameter that is handled mutate the existing instance. Your record needs to be build with the list of things so you can't by desing provide indexed values. You would be able to call an API on your record to do the same thing if you did this manually. |
I think it's possible to support that since list is present with Here is the workaround, @anthsup You can check null in record constructor. record Record(List<String> list) {
public Record {
if (list == null) {
list = new ArrayList<>();
}
}
}
static class PojoWithoutSetter {
private final List<String> list;
PojoWithoutSetter(List<String> list) {
if (list == null)
list = new ArrayList<>();
this.list = list;
}
public List<String> getList() {
return this.list;
}
} |
|
Actually superseded by #32426. |
Affects: \every version up to 3.3.0-SNAPSHOT (tested with both 3.2.3 and 3.3.0-SNAPSHOT)
Hello!
I came across an issue with nested records used as query parameters. Here is what my Controller looks like:
RecordParamHolder
looks like this (as the names state 1st one is a POJO and the 2nd one is a record):Both
PojoListParam
andRecordListParam
contain one field of typeList<String>
. And so when I try to pass a list of params like this:GET localhost:8080/api?recordParamName.in%5B0%5D=abc
(square brackets escaped) I see that the list is not being resolved and isNULL
. However, similar request works fine for nested POJO class –GET localhost:8080/api?pojoParamName.in%5B0%5D=abc
.I know that I can pass a list like this:
paramName=value1,value2,etc.
, but this approach doesn't work when there is a comma in any of the values (see this issue for example #29411). I know I can also pass it like thisparamName=value1¶mName=value2&etc
, but I was told it's inconvenient to build such request from JavaScript because paramName is being overwritten and only the last one is passed.Anyway, I believe it should work properly for nested records just as it works for nested POJOs. Attaching a Minimal Reproducible Example.
Thanks in advance!
The text was updated successfully, but these errors were encountered: