3

I have a request like:

example.com/search?sort=myfield1,-myfield2,myfield3

I would like to split those params to bind a List<String> sort in my controller or List<SortParam> where SortParam is the class with fields like: name (String) and ask (boolean).

So the final controller would look like this:

@RequestMapping(value = "/search", method = RequestMethod.GET)
public ResponseEntity<MyResponse> search(@RequestParam List<String> sort) {

    //...
}

or

@RequestMapping(value = "/search", method = RequestMethod.GET)
public ResponseEntity<MyResponse> search(@RequestParam List<SortParam> sort) {

    //...
}

Is there a way to make it?

UPDATE:

The standard way of passing parameters does not satisfy my requirements. I.e. I cannot use sort=myfield1&sort=-myfield2&sort=myfield3. I have to use comma separated names.
Also, I do understand that I can accept @RequestParam String sort in my controller and then split the string inside the controller like sort.split(",") but it also doesn't solve the above problem.

Oleksandr
  • 3,574
  • 8
  • 41
  • 78
  • 3
    The correct, standard way to do this would be to use `/search?sort=myfield1&sort=-myfield2&sort=myfield3`. But splitting on a comma isn't such a hard task either. – JB Nizet Aug 14 '18 at 11:50
  • Yes, splitting by comma isn't hard. I.e. just `str.split(",")` but I wonder if I can split such parameters before they arrive in the controller (not in the controller). – Oleksandr Aug 14 '18 at 12:05
  • I would like to use `List sort` in my controller, but the standard way of passing parameters does not satisfy my requirements. I have to use comma separated names. – Oleksandr Aug 14 '18 at 12:08

3 Answers3

6

Its just a simple Type Covertion task. Spring defines an SPI (Service Provider Interface) to implement type conversion logic. For your specific problem you can define your Type Conversion logic by implementing Converter interface.

@Component
public class StringToListConverter implements Converter<String, List<String>> {

    @Override
    public List<String> convert(String source) {
        return Arrays.asList(source.split(","));
    }
}

You can also convert your request parameter to List<SortPram> according your logic (but I am not sure about your that logic from your question). This is it! Now Spring get known that how to bind your request paramter to a list.

@RequestMapping(value = "/search", method = RequestMethod.GET)
public ResponseEntity<MyResponse> participants(@RequestParam("sort") List<String> sort) {
     // .. do your logic
}

There are many more ways to define you custom data binder. Check this

Shafin Mahmud
  • 3,831
  • 1
  • 23
  • 35
5

Yes, you can certainly do that, you're almost there.

@RequestMapping(value = "/search", method = RequestMethod.GET)
public ResponseEntity<MyResponse> participants(@RequestParam("sort") List<String> sort) {

    //...
}

You should now be able to call your service like this (if search is located at your root, otherwise adapt according to your situation):

curl "localhost:8080/search?sort=sortField1&sort=sortField2&sort=sortField3"

Hope this helps!

EDIT Sorry, I have read your comments and what you need is clear to me now. I have created a workaround for you that is almost what you want I think.

So first a SortParams class:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SortParams {
  private List<SortParam> sortParamList;

  public SortParams(String commaSeparatedString) {
    sortParamList = Arrays.stream(commaSeparatedString.split(","))
      .map(p -> SortParam.valueOf(p))
      .collect(Collectors.toList());
  }

  public List<SortParam> getSortParamList() {
    return this.sortParamList;
  }

  public enum SortParam {
    FOO, BAR, FOOBAR;
  }
}

Then your controller could look like this:

@RequestMapping(value = "/search", method = RequestMethod.GET)
public ResponseEntity<List<SortParams.SortParam>> search(@RequestParam("sort") SortParams sort) {
    return ResponseEntity.ok(sort.getSortParamList());
}

Now your SortParams object has a list of SortParam:

curl "localhost:8080/search?sort=FOO,BAR"

["FOO","BAR"]

Would something like this fit what you're looking for?

Jonck van der Kogel
  • 2,983
  • 3
  • 23
  • 30
  • Thank you for helping, but this solution doesn't solve the above problem. Please check comments under the question. Also, I've updated the question to be more clear. – Oleksandr Aug 14 '18 at 13:41
  • Thank you! It is exactly what I need! – Oleksandr Aug 14 '18 at 16:55
  • The custom "SortParams" Type with converter logic in the constructor works just fine - but after that I saw the next answer and I think implementing that `org.springframework.core.convert.converter.Converter` Interface is the cleaner approach. No need to call `.getSortParamList()` on that wrapper object but just directly get what you need in the controller. – Ralf Dec 01 '20 at 09:08
0

It could be helpfull if Kotlin

private const val DELIMITER: Char = ':'
private val DEFAULT_DIRECTION: Sort.Direction = Sort.Direction.ASC

private fun parseFrom(source: String): Sort.Order = if (source.contains(DELIMITER)) {
    Sort.Order(Sort.Direction.fromString(source.substringAfter(DELIMITER)), source.substringBefore(DELIMITER))
} else Sort.Order(DEFAULT_DIRECTION, source)

// if few sort paremeters
@Component
class RequestSortConverterArray : Converter<Array<String>, Sort> {
    override fun convert(source: Array<String>): Sort? = if (source.isEmpty()) Sort.unsorted()
    else source.map { parseFrom(it) }.let { Sort.by(it) }
}

// if single sort paremeter
@Component
class RequestSortConverterString : Converter<String, Sort> {
    override fun convert(source: String): Sort? = if (source.isEmpty()) Sort.unsorted()
    else Sort.by(parseFrom(source))
}

...

@GetMapping
fun search(
  @RequestParam(required = false, defaultValue = "0") page: Int,
  @RequestParam(required = false, defaultValue = "20") size: Int,
  @RequestParam(required = false, defaultValue = "myfield1:asc") sort: Sort
) {
  val pageable = PageRequest.of(page, size, sort)
  // make a search by repository
}

avvensis
  • 917
  • 10
  • 20