funnelCONN
funnelCONN

Reputation: 149

Collect key values from array to map without duplicates

My app gets some string from web service. It's look like this:

name=Raul&city=Paris&id=167136

I want to get map from this string:

{name=Raul, city=Paris, id=167136}

Code:

Arrays.stream(input.split("&"))
          .map(sub -> sub.split("="))
          .collect(Collectors.toMap(string-> string[0]), string -> string[1]));

It's okay and works in most cases, but app can get a string with duplicate keys, like this:

name=Raul&city=Paris&id=167136&city=Oslo

App will crash with following uncaught exception:

Exception in thread "main" java.lang.IllegalStateException: Duplicate key city (attempted merging values Paris and Oslo)

I tried to change collect method:

.collect(Collectors.toMap(tokens -> tokens[0], tokens -> tokens[1]), (r, strings) -> strings[0]);

But complier says no:

Cannot resolve method 'collect(java.util.stream.Collector<T,capture<?>,java.util.Map<K,U>>, <lambda expression>)'

And Array type expected; found: 'T'

I guess, it's because I have an array. How to fix it?

Upvotes: 0

Views: 400

Answers (3)

Abdelghani Roussi
Abdelghani Roussi

Reputation: 2817

If you want to add value of duplicated keys together and group them by key (since app can get a string with duplicate keys), instead of using Collectors.toMap() you can use a Collectors.groupingBy with custom collector (Collector.of(...)) :

String input = "name=Raul&city=Paris&city=Berlin&id=167136&id=03&id=505";

Map<String, Set<Object>> result = Arrays.stream(input.split("&"))
                .map(splitedString -> splitedString.split("="))
                .filter(keyValuePair -> keyValuePair.length() == 2)
                .collect(
                        Collectors.groupingBy(array -> array[0], Collector.of(
                                () -> new HashSet<>(), (set, array) -> set.add(array[1]),
                                (left, right) -> {
                                    if (left.size() < right.size()) {
                                        right.addAll(left);
                                        return right;
                                    } else {
                                        left.addAll(right);
                                        return left;
                                    }
                                }, Collector.Characteristics.UNORDERED)
                        )
                );

This way you'll get :

result => size = 3
 "city" -> size = 2 ["Berlin", "Paris"]
 "name" -> size = 1 ["Raul"]
 "id"   -> size = 3 ["167136","03","505"]

Upvotes: 0

Vahe Gharibyan
Vahe Gharibyan

Reputation: 5683

You can achieve the same result using kotlin collections

val res = message
        .split("&")
        .map {
            val entry = it.split("=")
            Pair(entry[0], entry[1])
        }
println(res)
println(res.toMap()) //distinct by key

The result is

[(name, Raul), (city, Paris), (id, 167136), (city, Oslo)]

{name=Raul, city=Oslo, id=167136}

Upvotes: -1

sprinter
sprinter

Reputation: 27946

You are misunderstanding the final argument of toMap (the merge operator). When it find a duplicate key it hands the current value in the map and the new value with the same key to the merge operator which produces the single value to store.

For example, if you want to just store the first value found then use (s1, s2) -> s1. If you want to comma separate them, use (s1, s2) -> s1 + ", " + s2.

Upvotes: 2

Related Questions