[FIXED] Using a MultiMap on spring boot controller

Issue

I have the following payload:

{
    "KeyA": "ValueA",
    "KeyB": "ValueB",
    "KeyC": "ValueC"
    ...
    "KeyZ": "ValueZ"
}

That I am able to parse properly in a spring boot controller with:

@PostMapping
  public ResponseEntity<Object> createABC(
      @RequestBody final Map<MyDTO, MyDTO> map
  ) {

For the sake of brevity, consider MyDTO as:

@Data
@ToString(includeFieldNames=false)
public class MyDTO implements Comparable<MyDTO> {
  private final String name;

The problem is:

I can have duplicated keys on the input:

{
    "KeyA": "ValueA",
    "KeyA": "ValueA.1",
    ...
}

Which then gets deduplicated by the native Java Map implementation – only the second key gets saved and I am okay with that. I found out Guava Multimap that would allow me to do it. This would allow me to accept a map with duplicates and let me execute the custom logic on the controller/service layer.

The problem is that I can’t find how to make spring boot parse this map in the controller without reformatting the request, I would like to keep it as it is. I was able to add the Object Mapper but I am failing to be able to receive this map from the request at the controller and use it.

Has anyone faced this issue before?

Solution

The problem is that I can't find how to make spring boot parse this map in the controller without reformatting the request

I’m not sure what you meant by this, but I assume that you are sending the request like this :

enter image description here

If you want to use com.google.common.collect.Multimap
there is no other way to send a request.

If you want to send a request in the form you mentioned in the question (withot [] brackets), as far as I can see, you have two options:

  1. Use org.apache.commons.collections4.MultiValuedMap with custom deserilizer;

Here is how your custom deserilizer should look like:

import com.example.demo.dto.MyDTO;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;

import java.io.IOException;

class MultiValuedMapJsonDeerializer extends JsonDeserializer<MultiValuedMap> {

    @Override
    public MultiValuedMap deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException {
        MultiValuedMap<MyDTO, MyDTO> myDTOMyDTOMultiValuedMap = new ArrayListValuedHashMap<>();
        if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
            throw new IOException("invalid start marker");
        }

        while (jp.nextToken() != JsonToken.END_OBJECT) {
            String fieldname = jp.getCurrentName();
            jp.nextToken();
            String value = jp.getText();
            MyDTO key = new MyDTO(fieldname);
            MyDTO myDTO = new MyDTO(value);
            myDTOMyDTOMultiValuedMap.put(key, myDTO);
        }
        jp.close();

        return myDTOMyDTOMultiValuedMap;
    }

    @Override
    public Class<MultiValuedMap> handledType() {
        return MultiValuedMap.class;
    }
}

Here is how endpoint shoud look like:

@PostMapping("/test")
    public void createABC(@RequestBody final MultiValuedMap<MyDTO, MyDTO> map) {
        System.out.println(map);
    }

When you hit this endpoint, you should see output like this:

{MyDTO(keyB)=[MyDTO(B)], MyDTO(keyC)=[MyDTO(C), MyDTO(D)], MyDTO(keyA)=[MyDTO(A)]}
  1. Use org.springframework.util.MultiValueMap

If you want to use this class, you shoud send your request body as x-www-form-urlencoded. Here is an example from Postman:

enter image description here

And finally, if you decide to use this option here is an example how controller method should look like:

@PostMapping("/multiValueMap")
    public void multiValueMap(@RequestBody MultiValueMap<MyDTO, MyDTO> multiValueMap) {
        System.out.println(multiValueMap);
       
    }

Using this approach, you should see output like this:

{KeyB=[b], KeyA=[a, c]}

Answered By – Nemanja

Answer Checked By – David Goodson (Easybugfix Volunteer)

Leave a Reply

(*) Required, Your email will not be published