I have a class named ConfigKey
public class ConfigKey {
String code;
String key;
String value;
//omit setter and getter
}
I want to convert List<ConfigKey> to Map<String, Map<String, Object>>, here is my method definition
public Map<String, Map<String, Object> convert (List<ConfigKey> list) {
return list.stream().collect(Collectors.groupingBy(ConfigKey::getCode,
Collectors.toMap(ConfigKey::getKey, ConfigKey::getValue)));
}
however I want to do some changes, for each ConfigKey put another key to the map, e.g.
{ "code": "code1","key", "key1", "value": "value1"}
to Map
{"code1": {"key1":"value1", "prefix_key1": "value1" }
is there any API to do it like bellow:
public Map<String, Map<String, Object> convert (List<ConfigKey> list) {
return list.stream().collect(Collectors.groupingBy(ConfigKey::getCode,
Collectors.toMap("prefix_" + ConfigKey::getKey, ConfigKey::getValue))
Collectors.toMap(ConfigKey::getKey, ConfigKey::getValue)));
}
You can make use of the Collector.of() factory method, which allows you to create your own collector:
public Map<String, Map<String, Object> convert (List<ConfigKey> list) {
return list.stream().collect(Collectors.groupingBy(ConfigKey::getCode, Collector.of(
HashMap::new, (m, c) -> {
m.put(c.getKey(), c.getValue());
m.put("prefix_" + c.getKey(), c.getValue());
}, (a, b) -> {
a.putAll(b);
return b;
}
)));
}
But honestly that seems a bit messy, and maybe a normal loop would've been better. The streams intention was to provide an api which does things in a more readable manner, but when you have to hackaround that construct, by introducing some extremely unreadable logic then it is almost always the better option to just do it the old way:
public Map<String, Map<String, Object> convert (List<ConfigKey> list) {
Map<String, Map<String, Object>> map = new HashMap<>();
for (ConfigKey ck : list) {
Map<String, Object> inner = map.computeIfAbsent(ck.getCode(), k -> new HashMap<>());
inner.put(ck.getKey(), ck.getValue());
inner.put("prefix_" + ck.getKey(), ck.getValue());
}
return map;
}
You can first add the new entries to the map and then group them:
private Map<String, Map<String, Object>> convert(List<ConfigKey> list) {
new ArrayList<>(list).stream().map(configKey -> new ConfigKey(configKey.getCode(), "prefix_" + configKey.getKey(), configKey.getValue())).forEachOrdered(list::add);
return list.stream().collect(Collectors.groupingBy(ConfigKey::getCode,
Collectors.toMap(ConfigKey::getKey, ConfigKey::getValue)));
}
I cloned the list (in order to prevent ConcurrentModificationException), then changed the keys to the "new" ones (with map) and added them to the original list - forEachOrdered(list::add).
Because the 'code' field was not changed, both entries will use it which results in 2 entries in the map
Related
I have a map read from my static config in the following format:
Map<String, Map<String, List<String>>> dependentPluginEntityMapString.
The string values in this map are actually from ENUMs and the correct and required representation of the map is Map<ENUM_A, Map<ENUM_A, List<ENUM_B>>>.
ENUM_A {
APPLE, BANANA
}
ENUM_B {
ONION, RADDISH
}
How can I convert the map of strings to the one with enums Map<ENUM_A, Map<ENUM_A, List<ENUM_B>>> for more type safety?
I know, I can iterate on the string map (using for or streams) and create a new map with enums as required but looking for a better/more efficient and an elegant way of doing that?
This is my brute solution. Can i do better?
final Map<ENUM_A, Map<ENUM_A, List<ENUM_B>>> dependentPluginEntityMap = new HashMap<>();
for (Map.Entry<String, Map<String, List<String>>> dependentPluginEntry:
dependentPluginEntityMapFromAppConfig.entrySet()) {
final Map<ENUM_A, List<ENUM_B>> independentPluginMapForEntry = new HashMap<>();
if (MapUtils.isNotEmpty(dependentPluginEntry.getValue())) {
for (Map.Entry<String, List<String>> independentPluginEntry:
dependentPluginEntry.getValue().entrySet()) {
if (CollectionUtils.isNotEmpty(independentPluginEntry.getValue())) {
independentPluginMapForEntry.put(ENUM_A.valueOf(independentPluginEntry.getKey()),
independentPluginEntry.getValue().stream().map(value -> ENUM_B.valueOf(value))
.collect(Collectors.toList()));
}
}
}
dependentPluginEntityMap.put(ENUM_A.valueOf(dependentPluginEntry.getKey()),
independentPluginMapForEntry);
}
Should I convert the map of strings to ENUMMAP , instead of map with enum keys? Will it work with my nested map structure?
Any leads apprecciated.
Multi-level Map conversions are easier if you define simple utility methods to manage transformation from Map<K,V> to Map<X,Y> and List<X> to List<Y>:
static <X,Y> List<Y> toList(List<X> list, Function<X,Y> valueMapper) {
return list.stream().map(valueMapper).toList();
}
static <K,V,X,Y> Map<X, Y> toMap(Map<K, V> map, Function<K,X> keyMapper, Function<V,Y> valueMapper) {
return map.entrySet().stream()
.collect(Collectors.toMap(entry -> keyMapper.apply(entry.getKey()),
entry -> valueMapper.apply(entry.getValue())));
}
With these definitions your transformation is reduced to applications of the above:
Map<String, Map<String, List<String>>> in = Map.of(
"APPLE", Map.of("APPLE", List.of("RADDISH")
,"BANANA", List.of("ONION", "RADDISH"))
,"BANANA", Map.of("APPLE", List.of("ONION", "RADDISH")
, "BANANA", List.of("ONION"))
);
Map<ENUM_A, Map<ENUM_A, List<ENUM_B>>> map
= toMap(in, ENUM_A::valueOf,
m -> toMap(m, ENUM_A::valueOf,
list -> toList(list, ENUM_B::valueOf)));
=>
map ={BANANA={BANANA=[ONION]
, APPLE=[ONION, RADDISH]}
, APPLE={BANANA=[ONION, RADDISH]
, APPLE=[RADDISH]}}
I am iterating through a List of Hashmap to find the required HashMap object using the following code.
public static Map<String, String> extractMap(List<Map<String, String>> mapList, String currentIp) {
for (Map<String, String> asd : mapList) {
if (asd.get("ip").equals(currentIp)) {
return asd;
}
}
return null;
}
I was thinking about using Java 8 stream. This is the code I used to display the required object.
public static void displayRequiredMapFromList(List<Map<String, String>> mapList, String currentIp) {
mapList.stream().filter(e -> e.get("ip").equals(currentIp)).forEach(System.out::println);
}
I couldn't get the required Map from the stream using following code
public static Map<String, String> extractMapByStream(List<Map<String, String>> mapList, String currentIp) {
return mapList.stream().filter(e -> e.get("ip").equals(currentIp))
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
}
This causes syntax error Type mismatch: cannot convert from Map to Map. What do I have to put here to get Map?
You don't want to .collect anything. You want to find the first map that matches the predicate.
So you should use .findFirst() instead of .collect().
toMap() is for building a Map from the elements in the stream.
But you don't want to do that, each element is already a Map.
This will will work, the other examples without orElse() don't compile (at least they don't in my IDE).
mapList.stream()
.filter(asd -> asd.get("ip").equals(currentIp))
.findFirst()
.orElse(null);
The only thing I would add as a suggestion is to return Collections.emptyMap(), this will save a null check in the calling code.
To get the code to compile without orElse you need to change the method signature to:
public static Optional<Map<String, String>> extractMap(List<Map<String, String>> mapList, String currentIp)
User this
public static Map<String, String> extractMapByStream(List<Map<String, String>> mapList, String currentIp) {
return mapList.stream().filter(e -> e.get("ip").equals(currentIp))
.findFirst().get();
}
Suppose I have having Json response like this:
{
"status": true,
"data": {
"29": "Hardik sheth",
"30": "Kavit Gosvami"
}
}
I am using Retrofit to parse Json response. As per this answer I will have to use Map<String, String> which will give all the data in Map. Now what I want is ArrayList<PojoObject>.
PojoObject.class
public class PojoObject {
private String mapKey, mapValue;
public String getMapKey() {
return mapKey;
}
public void setMapKey(String mapKey) {
this.mapKey = mapKey;
}
public String getMapValue() {
return mapValue;
}
public void setMapValue(String mapValue) {
this.mapValue = mapValue;
}
}
What is the best way to convert a Map<key,value> to a List<PojoObject>?
If you can expand your class to have a constructor taking the values as well:
map.entrySet()
.stream()
.map(e -> new PojoObject(e.getKey(), e.getValue()))
.collect(Collectors.toList());
If you can't:
map.entrySet()
.stream()
.map(e -> {
PojoObject po = new PojoObject();
po.setMapKey(e.getKey());
po.setMapValue(e.getValue());
return po;
}).collect(Collectors.toList());
Note that this uses Java 8 Stream API.
Looks like Java has exact POJO Map.Entry like you want. Hence, you can extract the entry set from map and iterate over the entry set like below or you can further convert the set to list like in next snippet and continue with your processing.
//fetch entry set from map
Set<Entry<String, String>> set = map.entrySet();
for(Entry<String, String> entry: set) {
System.out.println(entry.getKey() +"," + entry.getValue());
}
//convert set to list
List<Entry<String, String>> list = new ArrayList(set);
for(Entry<String, String> entry: list) {
System.out.println(entry.getKey() +"," + entry.getValue());
}
Try this
List<Value> list = new ArrayList<Value>(map.values());
Or
hashMap.keySet().toArray(); // returns an array of keys
hashMap.values().toArray(); // returns an array of values
Should be noted that the ordering of both arrays may not be the same.
or
hashMap.entrySet().toArray();
You can use this method to convert map to list
List<PojoObject> list = new ArrayList<PojoObject>(map.values());
Assuming:
Map <Key,Value> map;
ArrayList<Map<String,String>> list = new ArrayList<Map<String,String>>();
this may be the best way.
Given that we have the following function:
public Map<String, List<String>> mapListIt(List<Map<String, String>> input) {
Map<String, List<String>> results = new HashMap<>();
List<String> things = Arrays.asList("foo", "bar", "baz");
for (String thing : things) {
results.put(thing, input.stream()
.map(element -> element.get("id"))
.collect(Collectors.toList()));
}
return results;
}
Is there some way I could clean this up by binding "id" to a Map::get method reference?
Is there a more stream-y way to write this functionality?
As far as I can tell what you are intending is that this function returns a map from a defined list of strings to a list of all elements with key "id" in a list of input maps. Is that correct?
If so it could be significantly simplified as the value for all keys will be the same:
public Map<String, List<String>> weirdMapFunction(List<Map<String, String>> inputMaps) {
List<String> ids = inputMaps.stream()
.map(m -> m.get("id")).collect(Collectors.toList());
return Stream.of("foo", "bar", "baz")
.collect(Collectors.toMap(Function.identity(), s -> ids));
}
If you wish to use a method reference (which is my interpretation of your question about 'binding') then you will need a separate method to reference:
private String getId(Map<String, String> map) {
return map.get("id");
}
public Map<String, List<String>> weirdMapFunction(List<Map<String, String>> inputMaps) {
List<String> ids = inputMaps.stream()
.map(this::getId).collect(Collectors.toList());
return Stream.of("foo", "bar", "baz")
.collect(Collectors.toMap(Function.identity(), s -> ids));
}
However I'm guessing that you intended to use the items in the list as the keys (rather than "id") in which case:
public Map<String, List<String>> weirdMapFunction(List<Map<String, String>> inputMaps) {
return Stream.of("foo", "bar", "baz")
.collect(Collectors.toMap(Function.identity(), s -> inputMaps.stream()
.map(m -> m.get(s)).collect(Collectors.toList())));
}
I have Map<String, Map<String, String>> myMap in my Java 8 class. I need to navigate to a leaf String like myMap['keyA']['keyB'], returning null if either 'keyA' or 'keyB' does not exist in the correlating Map.
In groovy I would use myMap?.keyA?.keyB and be done with it. I understand that Java 8's Optional<T> brings similar behavior into java. Is there a way to use this new behavior to concisely mimic the groovy functionality? If not, is there another concise way to get this behavior in Java 8, or am I still stuck with elaborate procedural code?
String valueOrNull = Optional.ofNullable(myMap.get("keyA"))
.map(x -> x.get("keyB"))
.orElse(null);
First, it wraps the results of the first lookup in an Optional, which acts as a monad. If you add a third layer (myMap.?keyA.?keyB.?keyC), it would look like this:
String valueOrNull = Optional.ofNullable(myMap.get("keyA"))
.map(x -> x.get("keyB"))
.map(x -> x.get("keyC"))
.orElse(null);
You can use Optional's ofNullable method to create an Optional that may or may not represent a null value. Then you can use the map method that will, with a Function, map the result to a new value if the value wasn't already null.
Here, I supply a Function as a lambda expression to get the value from the second Map using the second key.
Optional<String> result = Optional.ofNullable(myMap.get("keyA")).map(m -> m.get("keyB"));
From there you can see if the Optional has a value with isPresent(), and if so, get it with get().
Testing:
public static Optional<String> method(Map<String, Map<String, String>> map,
String key1, String key2)
{
return Optional.ofNullable(map.get(key1)).map(m -> m.get(key2));
}
Calling Code:
Map<String, Map<String, String>> myMap = new HashMap<>();
Map<String, String> inner = new HashMap<>();
inner.put("one", "two");
myMap.put("three", inner);
System.out.println(method(myMap, "three", "one"));
System.out.println(method(myMap, "three", "dne"));
System.out.println(method(myMap, "dne", "dne"));
Output:
Optional[two]
Optional.empty
Optional.empty
Interesting question.
You can consider using recursion.
/**
* Finds the value of a node in nested maps.
* #return leaf value or null if none
*/
public <K, V> V getValueFromKeys(Map<K, V> map, K... keys) {
V value = map.getOrDefault(keys[0], null);
if (keys.length == 1) return value;
if (value instanceof Map) {
K[] remainingKeys = Arrays.copyOfRange(keys, 1, keys.length);
return getValueFromKeys((Map<K, V>) value, remainingKeys);
}
return null;
}
This will work with Java >= 8 (you can easily adapt it to previous versions).
Bonus (needs Guava):
#Test
public void getValueFromKeys_level1() {
Map<String, String> mapLevel1 = ImmutableMap.of("key1", "value1");
assertEquals("value1", getValueFromKeys(mapLevel1, "key1"));
assertNull(getValueFromKeys(mapLevel1, null));
assertNull(getValueFromKeys(mapLevel1, ""));
assertNull(getValueFromKeys(mapLevel1, "wrong"));
assertNull(getValueFromKeys(mapLevel1, "key1", "wrong"));
}
#Test
public void getValueFromKeys_level2() {
Map<String, Map<String, String>> mapLevel2 = ImmutableMap.of("key1", ImmutableMap.of("subkey1", "value1"));
assertEquals("value1", getValueFromKeys(mapLevel2, "key1", "subkey1"));
assertNull(getValueFromKeys(mapLevel2, null));
assertNull(getValueFromKeys(mapLevel2, ""));
assertNull(getValueFromKeys(mapLevel2, "wrong"));
assertNull(getValueFromKeys(mapLevel2, "key1", "wrong"));
assertNull(getValueFromKeys(mapLevel2, "key1", "subkey1", "wrong"));
assertTrue(getValueFromKeys(mapLevel2, "key1") instanceof Map);
}
#Test
public void getValueFromKeys_level3() {
Map<String, Map<String, Map<String, String>>> mapLevel3 = ImmutableMap.of("key1", ImmutableMap.of("subkey1", ImmutableMap.of("subsubkey1", "value1")));
assertEquals("value1", getValueFromKeys(mapLevel3, "key1", "subkey1", "subsubkey1"));
assertNull(getValueFromKeys(mapLevel3, null));
assertNull(getValueFromKeys(mapLevel3, ""));
assertNull(getValueFromKeys(mapLevel3, "wrong"));
assertNull(getValueFromKeys(mapLevel3, "key1", "wrong"));
assertNull(getValueFromKeys(mapLevel3, "key1", "subkey1", "wrong"));
assertNull(getValueFromKeys(mapLevel3, "key1", "subkey1", "subsubkey1", "wrong"));
}