Home > Mobile >  How to convert POJO to MultiValueMap<String, String>?
How to convert POJO to MultiValueMap<String, String>?

Time:12-22

I want to convert POJO to MultiValueMap<String, String>(RequestParams).

But I have some problem.

  • POJO class
public class Foo {
  String fooStr;

  int fooInt;

  List<String> fooList;
}
  • Convert code
public static MultiValueMap<String, String> convert( Object obj ) {
  MultiValueMap<String, String> requestParamMultiValueMap = new LinkedMultiValueMap<>();
  
  Map<String, String> requestParamMap = new ObjectMapper()
      .convertValue( obj, new TypeReference<Map<String, String>>() {} ); // Exception occurs here
  requestParamMultiValueMap.setAll( requestParamMap );
  
  return requestParamMultiValueMap;
}

When Foo class has only String member variables, it doesn't matter.

But Foo class has List<String> member variables, and it causes IllegalArgumentException: Cannot deserialize value of type `java.lang.String` from Array value (token `JsonToken.START_ARRAY`).

How can I fix it?

CodePudding user response:

This might a little slow but you can try this.

public MultiValueMap<String, String> convertToMultiValueMap(Object pojo) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();

Field[] fields = pojo.getClass().getDeclaredFields();
for (Field field : fields) {
    field.setAccessible(true);
    String key = field.getName();
    Object value = field.get(pojo);
    if (value != null) {
        map.add(key, value.toString());
    }
}

return map;
}

This method uses reflection to get a list of all the fields in the POJO and adds each field to the map as a key-value pair.

CodePudding user response:

This can't be done automatically without customization, because the problem is too broad: each type needs to be converted to a list of strings, which is not obvious. Here we have some trivial cases (integer and string), but generally it would be good to control this conversion.

Here is a simple example where first we do a default conversion to Map<String, Object>, handled naturally by Jackson; then the custom if-else step follows, where each type goes through a special conversion. This step should be extended to fit the exact needs (and types) of the application. I guess proper test coverage would do the trick, because we can't rely 100% on static typing in this case.

It is important to notice that in case the Collection has other collections as elements the results depends on each particular collection "toString" method implementation, which probably could give unexpected results.

class TestJacksonConvert {

    private static ObjectMapper OM = new ObjectMapper();

    @Test
    void test() {
        var foo = new Foo();
        foo.fooStr = "abc";
        foo.fooInt = 21;
        foo.fooList = List.of("a", "B", "c");

        var converted = convert(foo);

        assertEquals(
            converted,
            Map.of(
                "fooStr", List.of("abc"),
                "fooInt", List.of("21"),
                "fooList", List.of("a", "B", "c")
            )
        );
    }

    public static LinkedMultiValueMap<String, String> convert(Object obj) {
        var multiMap = new LinkedMultiValueMap<String, String>();

        Map<String, Object> map = OM.convertValue(obj, new TypeReference<>() {});

        map.forEach((key, value) -> {
            if (value instanceof Collection<?> collection) {
                multiMap.put(key, collection.stream().map(Object::toString).toList());
            } else if (value != null) {
                multiMap.put(key, List.of(value.toString()));
            }
        });

        return multiMap;
    }
}
  • Related