retrofit
retrofit copied to clipboard
Maps do not support multi-values
hi, we are using retrofit 1.9 and we declared our endpoint as
@GET("/path")
Response get(@QueryMap Map<String, String> queryParams);
which works fine and is expanded to ex. host/path?rq=1&ra=2
for input {"rq": "1", "ra": "2"}
.
right now we need to support multivalue same as described here https://futurestud.io/blog/retrofit-multiple-query-parameters-of-same-name but for @QueryMap. and declaring endpoint as
@GET("/path")
Response get(@QueryMap Map<String, List<String>> queryParams);
doesn't help at all as value is expanded to its string equivalent ex. host/path?rq=[1,2]
for input {"rq": ["1", "2"]}
. we would like to get same behavior as for @Query ex. host/path?rq=1&rq=2
for input {"rq": ["1", "2"]}
.
is there any work around for now?
None of the maps support this currently, no.
is there any road map. we solve the issue adding another parameter with @Query annotation which supports multi value. but this is ugly
In my project I serialized my object via GSON to string and with @Query annotation I succeed to send it probably
it is not about serializing but properly handling multivalues for one key
@PartMap
does not support multi values as well. One trick around it is to extend Map
and overriding Map#entrySet()
, though this can break as it depends on the internals.
Most likely #1184 provides some help with this.
thanks! it seems will do the trick!
in the meantime, i hacked this together for anyone who absolutely needs this and can't wait - https://gist.github.com/mandybess/dca2e8a0527aff2d8e0688c17297c945
I have a simple trick.
public class ProxyRetrofitQueryMap extends HashMap<String, Object> {
public ProxyRetrofitQueryMap(Map<String, Object> m) {
super(m);
}
@Override
public Set<Entry<String, Object>> entrySet() {
Set<Entry<String, Object>> originSet = super.entrySet();
Set<Entry<String, Object>> newSet = new HashSet<>();
for (Entry<String, Object> entry : originSet) {
String entryKey = entry.getKey();
if (entryKey == null) {
throw new IllegalArgumentException("Query map contained null key.");
}
Object entryValue = entry.getValue();
if (entryValue == null) {
throw new IllegalArgumentException(
"Query map contained null value for key '" + entryKey + "'.");
}
else if(entryValue instanceof List) {
for(Object arrayValue:(List)entryValue) {
if (arrayValue != null) { // Skip null values
Entry<String, Object> newEntry = new AbstractMap.SimpleEntry<>(entryKey, arrayValue);
newSet.add(newEntry);
}
}
}
else {
Entry<String, Object> newEntry = new AbstractMap.SimpleEntry<>(entryKey, entryValue);
newSet.add(newEntry);
}
}
return newSet;
}
}
and use that class => @Querymap or @FieldMap.
hi, is this supported in the lastest retrofit version ?
@jm-lim's solution, converted for Kotlin:
class ProxyRetrofitQueryMap(m: MutableMap<String, Any>) : HashMap<String, Any>(m) {
override val entries: MutableSet<MutableMap.MutableEntry<String, Any>>
get() {
val originSet: Set<Map.Entry<String?, Any?>> = super.entries
val newSet: MutableSet<MutableMap.MutableEntry<String, Any>> = HashSet()
for ((key, entryValue) in originSet) {
val entryKey = key ?: throw IllegalArgumentException("Query map contained null key.")
// Skip null values
requireNotNull(entryValue) { "Query map contained null value for key '$entryKey'." }
if (entryValue is List<*>) {
for (arrayValue in entryValue) {
if (arrayValue != null) { // Skip null values
val newEntry: MutableMap.MutableEntry<String, Any> =
SimpleEntry(entryKey, arrayValue)
newSet.add(newEntry)
}
}
} else {
val newEntry: MutableMap.MutableEntry<String, Any> = SimpleEntry(entryKey, entryValue)
newSet.add(newEntry)
}
}
return newSet
}
}
Any update on this or should we go with custom solution? @JakeWharton
+1 on this issue. Please support Map<String, Iterable<Object>> (or any of guava, spring, commons-collections multimaps) as a parameter annotated with @QueryMap
btw - if your input already has data on the form Map<String, List<String>> the solution of @lgtout and @jm-lim could be reduced to something like:
private class ProxyRetrofitQueryMap(val original: Map<String, List<String>>) : AbstractMap<String, String>() {
override val entries: Set<Map.Entry<String, String>>
get() {
return original.entries.flatMap { (key, value) -> value.map { SimpleEntry(key, it) } }.toSet()
}
}