Skip to content

Fail to map GET query params in pojo  #25815

Closed as not planned
Closed as not planned
@Blackdread

Description

@Blackdread

Affects: Spring boot 2.4.0-SNAPSHOT
Affects: Spring boot 2.3.4.RELEASE
Affects: Spring boot 2.1.9.RELEASE


Same behavior with kotlin version 1.3.72 and 1.4.10
Tested with java 8


I try to map a GET request into a pojo class in kotlin.
The method param resolver fail to map properly the query params.

I used to have no problem to do this mapping in Java; I am not sure anymore but I think it was working before for Kotlin with Set/List/etc. Now it does not work anymore.

I don't think I have to implement my own HandlerMethodArgumentResolver for all my custom criterias, I use only basic fields, and java works but not kotlin.

I know about StringToCollectionConverter, DelimitedStringToCollectionConverter and etc but it does not seem to be working with Kotlin Data class (or normal class)

With examples below we see that if I repeat the key and use one value per key then I get good collection for java/kotlin but if I use comma separated values for kotlin then it will either throw due to cast fail either put the whole string as one value.

The behavior is different between Java and Kotlin; they should have same behavior (and so support same as Java -> comma separated values)


Do a get with:
localhost:8080/test/param/test-1?projectIdIn2=1,2,3&projectIdIn3=1,2,4&projectIdIn4=1,2,5&projectIdIn5=1,2,6&projectIdIn6=1,2,7&projectIdIn8=1,2,9&projectIdIn1=1&projectIdIn1=3

Fail with
2020-09-25 15:36:19.865 WARN 10780 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.ModelAttributeMethodProcessor$1: org.springframework.validation.BeanPropertyBindingResult: 2 errors Field error in object 'simpleCriteria' on field 'projectIdIn3': rejected value [1,2,4]; codes [typeMismatch.simpleCriteria.projectIdIn3,typeMismatch.projectIdIn3,typeMismatch.java.util.Set,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [simpleCriteria.projectIdIn3,projectIdIn3]; arguments []; default message [projectIdIn3]]; default message [Failed to convert value of type 'java.lang.String[]' to required type 'java.util.Set'; nested exception is java.lang.NumberFormatException: For input string: "1,2,4"] Field error in object 'simpleCriteria' on field 'projectIdIn5': rejected value [1,2,6]; codes [typeMismatch.simpleCriteria.projectIdIn5,typeMismatch.projectIdIn5,typeMismatch.java.util.List,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [simpleCriteria.projectIdIn5,projectIdIn5]; arguments []; default message [projectIdIn5]]; default message [Failed to convert value of type 'java.lang.String[]' to required type 'java.util.List'; nested exception is java.lang.NumberFormatException: For input string: "1,2,6"]]


Same request as above but with java pojo:
localhost:8080/test/param/test-java-1?projectIdIn2=1,2,3&projectIdIn3=1,2,4&projectIdIn4=1,2,5&projectIdIn5=1,2,6&projectIdIn6=1,2,7&projectIdIn8=1,2,9&projectIdIn1=1&projectIdIn1=3

It will return a JSON:
{"projectIdIn1":[1,3],"projectIdIn2":["1","2","3"],"projectIdIn5":[1,2,6],"projectIdIn6":["1","2","7"]}


Request for:
localhost:8080/test/param/test-3?projectIdIn2=1,2,3&projectIdIn3=1,2,4&projectIdIn4=1,2,5&projectIdIn5=1,2,6&projectIdIn6=1,2,7&projectIdIn7=1,2,8&projectIdIn8=1,2,9&enum2=beijing,shanghai&projectIdIn1=5&projectIdIn1=6

Result:
{"projectIdIn1":[5,6],"projectIdIn2":["1","2","3"],"projectIdIn3":[1,2,4],"projectIdIn4":["1","2","5"],"projectIdIn5":[1,2,6],"projectIdIn6":["1","2","7"],"projectIdIn7":[1,2,8],"projectIdIn8":["1","2","9"],"enum1":null,"enum2":["beijing","shanghai"],"enum3":null}



@RestController
@RequestMapping("/test/param")
class TestController {

    @GetMapping("/test-java-1")
    fun test1(criteria: JavaCriteria): JavaCriteria {
        return criteria
    }

    @GetMapping("/test-1")
    fun test1(criteria: SimpleCriteria): SimpleCriteria {
        return criteria
    }

    @GetMapping("/test-3")
    fun test3(
            @RequestParam projectIdIn1: Set<Long>? = null,
            @RequestParam projectIdIn2: Set<String>? = null,
            @RequestParam projectIdIn3: Set<ProjectId>? = null,
            @RequestParam projectIdIn4: Set<ProjectIdNew>? = null,
            @RequestParam projectIdIn5: List<Long>? = null,
            @RequestParam projectIdIn6: List<String>? = null,
            @RequestParam projectIdIn7: List<ProjectId>? = null,
            @RequestParam projectIdIn8: List<ProjectIdNew>? = null
    ): SimpleCriteria {
        return SimpleCriteria(
                projectIdIn1,
                projectIdIn2,
                projectIdIn3,
                projectIdIn4,
                projectIdIn5,
                projectIdIn6,
                projectIdIn7,
                projectIdIn8
        )
    }

}

typealias ProjectId = Long
typealias ProjectIdNew = String

data class SimpleCriteria(
        val projectIdIn1: Set<Long>? = null, // fails to convert 1,2
        val projectIdIn2: Set<String>? = null, // Get a list with one element "1,2"
        val projectIdIn3: Set<ProjectId>? = null, // fails to convert 1,2
        val projectIdIn4: Set<ProjectIdNew>? = null, // Get a list with one element "1,2"
        val projectIdIn5: List<Long>? = null, // fails to convert 1,2
        val projectIdIn6: List<String>? = null, // Get a list with one element "1,2"
        val projectIdIn7: List<ProjectId>? = null, // fails to convert 1,2
        val projectIdIn8: List<ProjectIdNew>? = null // Get a list with one element "1,2"
)

public class JavaCriteria {

    private Set<Long> projectIdIn1;
    private Set<String> projectIdIn2;
    private List<Long> projectIdIn5;
    private List<String> projectIdIn6;

    public JavaCriteria() {
    }

    public Set<Long> getProjectIdIn1() {
        return projectIdIn1;
    }

    public void setProjectIdIn1(final Set<Long> projectIdIn1) {
        this.projectIdIn1 = projectIdIn1;
    }

    public Set<String> getProjectIdIn2() {
        return projectIdIn2;
    }

    public void setProjectIdIn2(final Set<String> projectIdIn2) {
        this.projectIdIn2 = projectIdIn2;
    }

    public List<Long> getProjectIdIn5() {
        return projectIdIn5;
    }

    public void setProjectIdIn5(final List<Long> projectIdIn5) {
        this.projectIdIn5 = projectIdIn5;
    }

    public List<String> getProjectIdIn6() {
        return projectIdIn6;
    }

    public void setProjectIdIn6(final List<String> projectIdIn6) {
        this.projectIdIn6 = projectIdIn6;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        final JavaCriteria that = (JavaCriteria) o;
        return Objects.equals(projectIdIn1, that.projectIdIn1) &&
                Objects.equals(projectIdIn2, that.projectIdIn2) &&
                Objects.equals(projectIdIn5, that.projectIdIn5) &&
                Objects.equals(projectIdIn6, that.projectIdIn6);
    }

    @Override
    public int hashCode() {
        return Objects.hash(projectIdIn1, projectIdIn2, projectIdIn5, projectIdIn6);
    }

    @Override
    public String toString() {
        return "JavaCriteria{" +
                "projectIdIn1=" + projectIdIn1 +
                ", projectIdIn2=" + projectIdIn2 +
                ", projectIdIn5=" + projectIdIn5 +
                ", projectIdIn6=" + projectIdIn6 +
                '}';
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    status: invalidAn issue that we don't feel is validtheme: kotlinAn issue related to Kotlin support

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions