A flexible filter for collections or maps
Building a proxy for http requests I came across an interesting generic problem: filter or mutate a map but only for the occurence of some given set of keys.
That's a lot of if - else - switch
The going approach is to have a specialized method with a switch:
Map<String, String> transformMap(Map<String, String> input, String joker) {
Map<String, String> result = new HashMap<>();
input.entrySet().forEach(entry -> {
String key = entry.getKey();
String value = entry.getValue();
switch (key) {
case "a":
result.put(key, "alpha");
break;
case "b":
result.put(key, "beta");
break;
case "j":
result.put(key, joker + "!");
default:
result.put(key, value);
}
});
return result;
}
This gets big and hard to maintain fast, so I was thinking of a more neutral approach entertaining Function and Optional, hear me out.
It starts with the boilerplate class MapTransformer.java
. It contains a Map that uses String as a key and a Function taking String as input, returning an Optional of String. Furthermore it has a defaul function and a transform method that takes a Map for input and one for output as parameter.
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.function.Function;
public class MapTransformer {
private final TreeMap<String, Function<String, Optional<String>>> rules =
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
// Pass through by default
private Function<String, Optional<String>> defaultRule = Optional::of;
public Map<String, String> transform(Map<String, String> source, Map<String, String> destination) {
source.entrySet().forEach(entry -> {
String key = entry.getKey();
String value = entry.getValue();
Function<String, Optional<String>> rule = this.rules.getOrDefault(key, this.defaultRule);
rule.apply(value).ifPresent(v -> destination.put(key, v));
});
return destination;
}
public void setDefaultRule(Function<String, Optional<String>> defaultRule) {
this.defaultRule = defaultRule;
}
public void addRule(String key, Function<String, Optional<String>> rule) {
this.rules.put(key, rule);
}
}
Little nugget, that helped me dealing with headers, was to make the Map for looking up the rules a case insensitive TreeMap, so authorization
and Authorization
are the same key. The tubes can't make up their mind about it.
Also: The resulting Map is injected as parameter, so it will work with any class implementing map 9short of immutable ones of course).
So how does it work? Out of the box you get a 1:1 copy of your input, the fun starts when you prime the class before use.
MapTransformer mt = new MapTransformer();
mt.addRule("Authorization", s -> Optional.of(someValue));
mt.addRule("Accept", s -> Optional.empty());
mt.addRule("x-something", s -> Optional.of(s.toLowerCase()));
mt.transform(source, target);
The example above replaces the Authorization header, drops the Accept header and lowercases the x-something header. All only if such a key is encountered.
Would you like to drop unknown keys?mt.setDefaultRule(s -> Optional.empty())
Using Optional makes a cleaner read than performing null check (admitting the beauty is in the eye of the beholder):
rule.apply(value).ifPresent(v -> destination.put(key, v));
v.s.
String v = rule.apply(value);
if (v != null) {
destination.put(key, v);
}
This is not the ultimate class, it is merly a pattern. There are variations to be had: make MapTransformer.java
generic, pass a default set of rules in the constructor, add a Function to adjust the key before lookup, add a regex matcher etc.
Enjoy and as usual YMMV
Posted by Stephan H Wissel on 04 October 2025 | Comments (0) | categories: Java