I have a unit test that needs to check for a nested map value. I can get my assertion to work by pulling out the entry and matching the underlying Map, but I was looking for a clear way to show what the assertion is doing. Here is a very simplified test:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasEntry;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
public class MapContainsMapTest {
#Test
public void testMapHasMap() {
Map<String, Object> outerMap = new HashMap<String, Object>();
Map<String, Object> nestedMap = new HashMap<String, Object>();
nestedMap.put("foo", "bar");
outerMap.put("nested", nestedMap);
// works but murky
assertThat((Map<String, Object>) outerMap.get("nested"), hasEntry("foo", "bar"));
// fails but clear
assertThat(outerMap, hasEntry("nested", hasEntry("foo", "bar")));
}
}
It seems the problem is the outer map is being compared using hasEntry(K key, V value) while what I want to use is hasEntry(Matcher<? super K> keyMatcher, Matcher<? super V> valueMatcher). I am not sure how to coerce the assertion to use the second form.
Thanks in advance.
If you only want to put Map<String, Object> as values in your outerMap adjust the declaration accordingly. Then you can do
#Test
public void testMapHasMap() {
Map<String, Map<String, Object>> outerMap = new HashMap<>();
Map<String, Object> nestedMap = new HashMap<String, Object>();
nestedMap.put("foo", "bar");
outerMap.put("nested", nestedMap);
Object value = "bar";
assertThat(outerMap, hasEntry(equalTo("nested"), hasEntry("foo", value)));
}
Object value = "bar"; is necessary for compile reasons. Alternatively you could use
assertThat(outerMap,
hasEntry(equalTo("nested"), Matchers.<String, Object> hasEntry("foo", "bar")));
If You declare outerMap as Map<String, Map<String, Object>> you don't need the ugly cast. Like this:
public class MapContainsMapTest {
#Test
public void testMapHasMap() {
Map<String, Map<String, Object>> outerMap = new HashMap<>();
Map<String, Object> nestedMap = new HashMap<>();
nestedMap.put("foo", "bar");
outerMap.put("nested", nestedMap);
assertThat(outerMap.get("nested"), hasEntry("foo", "bar"));
}
}
I would probably extend a new Matcher for that, something like that (beware, NPEs lurking):
class SubMapMatcher extends BaseMatcher<Map<?,?>> {
private Object key;
private Object subMapKey;
private Object subMapValue;
public SubMapMatcher(Object key, Object subMapKey, Object subMapValue) {
super();
this.key = key;
this.subMapKey = subMapKey;
this.subMapValue = subMapValue;
}
#Override
public boolean matches(Object item) {
Map<?,?> map = (Map<?,?>)item;
if (!map.containsKey(key)) {
return false;
}
Object o = map.get(key);
if (!(o instanceof Map<?,?>)) {
return false;
}
Map<?,?> subMap = (Map<?,?>)o;
return subMap.containsKey(subMapKey) && subMap.get(subMapKey).equals(subMapValue);
}
#Override
public void describeTo(Description description) {
description.appendText(String.format("contains %s -> %s : %s", key, subMapKey, subMapValue));
}
public static SubMapMatcher containsSubMapWithKeyValue(String key, String subMapKey, String subMapValue) {
return new SubMapMatcher(key, subMapKey, subMapValue);
}
}
Try like this :
assertThat(nestedMap).contains(Map.entry("foo", "bar"));
assertThat(outerMap).contains(Map.entry("nested", nestedMap));
Related
public class First {
public final static Map<String, String> MAP = new HashMap<>();
static {
MAP.put("A", "1");
MAP.put("B", "2");
}
}
public class Second {
public static void main(String[] args) {
Class<?> clazz = Class.forName("First");
Field field = clazz.getField("MAP");
Map<String, String> newMap = (HashMap<String, String>) field.get(null); // Obviously doesn't work
}
}
Pretty much it. I have no trouble getting for example values of String variables, but I'm stuck with this one. Tryed to google it, failed. Also, if possible I'd like to get this Map without instantiating its class.
The only thing you are missing is to handle the exceptions for:
Class.forName("First");
clazz.getField("MAP");
field.get(null);
The code below get the static map field from First class. Here I'm just throwing/propagating the exceptions in the main method but you should handle the exceptions in a try/catch block accordingly.
class First {
public final static Map<String, String> MAP = new HashMap<>();
static {
MAP.put("A", "1");
MAP.put("B", "2");
}
}
public class Second {
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException,
ClassNotFoundException, NoSuchFieldException, SecurityException {
Class<?> clazz = Class.forName("First");
Field field = clazz.getField("MAP");
Map<String, String> newMap = (HashMap<String, String>) field.get(null); // Obviously doesn't work
System.out.println(newMap); //Prints {A=1, B=2}
}
}
Here the same example with a non static class:
package at.noe.szb;
import static org.junit.Assert.assertEquals;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
public class ReflectionTest {
private class First {
public Map<String, String> MAP = new HashMap<>();
First(){
MAP.put("A", "1");
MAP.put("B", "2");
}
}
#Test
public void testMap() throws Exception {
Class<?> clazz = Class.forName("at.noe.szb.First");
Field field = clazz.getField("MAP");
Map<String, String> newMap = (HashMap<String, String>) field.get(clazz);
assertEquals("{A=1, B=2}", newMap.toString());
}
}
I have a Java interface PlatformConfigurable. I also have two classes PlatformProducerConfig and PlatformConsumerConfig.
Later on, I need to add a common config to both that sets a property to an empty string:
private PlatformConfigurable disableHostNameVerificationConfig(PlatformConfigurable platformConfig) {
if (platformConfig instanceof PlatformProducerConfig) {
PlatformProducerConfig oldConfig = (PlatformProducerConfig) platformConfig;
Map<String, String> additionalConfig = oldConfig.additionalProperties();
Map<String, String> newConfig = new HashMap<>(Optional.ofNullable(additionalConfig).orElseGet(ImmutableMap::of));
newConfig.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
return oldConfig.toBuilder().additionalProperties(newConfig).build();
}
else if (platformConfig instanceof PlatformConsumerConfig) {
PlatformConsumerConfig oldConfig = (PlatformConsumerConfig) platformConfig;
Map<String, String> additionalConfig = platformConfig.additionalProperties();
Map<String, String> newConfig = new HashMap<>(Optional.ofNullable(additionalConfig).orElseGet(ImmutableMap::of));
newConfig.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
return oldConfig.toBuilder().additionalProperties(newConfig).build();
}
return platformConfig;
}
I am casting to producer or consumer config because the PlatformConfigurable interface doesn't have .toBuilder() or .build() methods declared in it, and I don't have access to modify the interface, as I can only implement it.
I would want to get rid of the duplicate code:
Map<String, String> additionalConfig = platformConfig.additionalProperties();
Map<String, String> newConfig = new HashMap<>(Optional.ofNullable(additionalConfig).orElseGet(ImmutableMap::of));
newConfig.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
return oldConfig.toBuilder().additionalProperties(newConfig).build();
I was thinking of using lambdas, but I am not 100% sure how to do it.
You could just refactor existing code like this:
private PlatfromConfigurable disableHostNameVerificationConfig(Platfromonfigurable platfromConfig) {
if (!(platformConfig instanceof PlatformProducerConfig) && !(platformConfig instanceof PlatformConsumerConfig)) {
return platformConfig;
}
Map<String, String> additionalConfig = platformConfig.additionalProperties();
Map<String, String> newConfig = new HashMap<>(Optional.ofNullable(additionalConfig).orElseGet(ImmutableMap::of));
newConfig.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
if (platformConfig instanceof PlatformProducerConfig) {
return ((PlatformProducerConfig)platformConfig).toBuilder().additionalProperties(newConfig).build();
}
return ((PlatformConsumerConfig)platformConfig).toBuilder().additionalProperties(newConfig).build();
}
Update
Another approach could be to extract functionality related to the builder to separate interfaces and use them in this way:
// 1. extend existing `PlatformConfigurable`
public interface BuilderedPlatformConfigurable extends PlatformConfigurable {
ConfigPlatformBuilder toBuilder();
}
// 2. provide builder interface with common implementation
public interface ConfigPlatformBuilder {
Map<String, String> additionalProperties = new HashMap<>();
BuilderedPlatformConfigurable build();
default ConfigPlatformBuilder additionalProperties(Map<String, String> properties) {
this.additionalProperties.clear();
this.additionalProperties.putAll(properties);
return this;
}
}
// 3. update PlatformConsumerConfig class (similarly, PlatformProducerConfig)
public class PlatformConsumerConfig implements BuilderedPlatformConfigurable {
private Map<String, String> additionalProperties = new HashMap<>();
#Override
public Map<String, String> additionalProperties() {
return additionalProperties;
}
public ConfigPlatformBuilder toBuilder() {
return new Builder();
}
public static class Builder implements ConfigPlatformBuilder {
public PlatformConsumerConfig build() {
PlatformConsumerConfig config = new PlatformConsumerConfig();
config.additionalPropertie.putAll(this.additionalProperties);
return config;
}
}
}
// 4. provide overloaded method
private PlatformConfigurable disableHostNameVerificationConfig(PlatformConfigurable platformConfig) {
return platformConfig;
}
private PlatformConfigurable disableHostNameVerificationConfig(BuilderedPlatformConfigurable platformConfig) {
Map<String, String> additionalConfig = platformConfig.additionalProperties();
Map<String, String> newConfig = new HashMap<>(Optional.ofNullable(additionalConfig).orElseGet(Map::of));
newConfig.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
return platformConfig.toBuilder().additionalProperties(newConfig).build();
}
Taking Alex Rudenko's answer a bit further, using generics:
private <P extends PlatformConfigurable> P disableHostNameVerificationConfig(P platformConfig, BiFunction<P, Map<String, String>, P> appender) {
Map<String, String> additionalConfig = platformConfig.additionalProperties();
Map<String, String> newConfig = new HashMap<>(Optional.ofNullable(additionalConfig).orElseGet(ImmutableMap::of));
newConfig.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
return appender.apply(platformConfig, newConfig);
}
This assumes that it is safe to do this for any subtype of PlatformConfigurable (and PlatformConfigurable itself).
Then invoke like:
disableHostNameVerificationConfig(
platformProducerConfig,
(p, config) -> p.toBuilder().setAdditionalConfig(config).build());
disableHostNameVerificationConfig(
platformConsumerConfig,
(p, config) -> p.toBuilder().setAdditionalConfig(config).build());
If you like, create helper methods to hide the BiFunctions:
private PlatformProducerConfig disableHostNameVerificationConfig(PlatformProducerConfig config) {
return disableHostNameVerificationConfig(
platformConfigurable,
(p, config) -> p.toBuilder().setAdditionalConfig(config).build());
}
private PlatformConsumerConfig disableHostNameVerificationConfig(PlatformConsumerConfig config) {
return disableHostNameVerificationConfig(
platformConfigurable,
(p, config) -> p.toBuilder().setAdditionalConfig(config).build());
}
Actually, I think a better way to do it would be without generics or lambdas: write a method which creates an updated map:
private static Map<String, String> newConfig(PlatformConfigurable platformConfig) {
Map<String, String> additionalConfig = platformConfig.additionalProperties();
Map<String, String> newConfig = additionalConfig != null ? new HashMap<>(additionalConfig) : new HashMap<>();
newConfig.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
return newConfig;
}
and then just have two overloads:
private PlatformProducerConfig disableHostNameVerificationConfig(PlatformProducerConfig config) {
return config.toBuilder().setAdditionalConfig(newConfig(config)).build();
}
private PlatformConsumerConfig disableHostNameVerificationConfig(PlatformConsumerConfig config) {
return config.toBuilder().setAdditionalConfig(newConfig(config)).build();
}
Adding one thing in Alex Rudenko's answer, and making different function to add different implementations of interfaces.
private PlatformConfigurable disableHostNameVerificationConfig(PlatformConfigurable platformConfig) {
if ((platformConfig == null)) {
return platformConfig;
}
Map<String, String> additionalConfig = platformConfig.additionalProperties();
Map<String, String> newConfig = new HashMap<>(Optional.ofNullable(additionalConfig).orElseGet(ImmutableMap::of));
newConfig.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
return PlatformConfigurableObject(platformConfig, newConfig);
}
So you can handle all instances in a different method, and whenever PlatfromXCofing classes are added later you only have to change this method. Single Responsibility Principle.
private PlatformConfigurable PlatformConfigurableObject(PlatformConfigurable platformConfig, Map<String, String> newConfig){
if (platformConfig instanceof PlatformProducerConfig) {
return ((PlatformProducerConfig)platformConfig).toBuilder().additionalProperties(newConfig).build();
} else if (platformConfig instanceof PlatformConsumerConfig){
return ((PlatformConsumerConfig)platformConfig).toBuilder().additionalProperties(newConfig).build();
} else{
return platformConfig;
}
}
I have a nested HashMap in this form:
{key1=val1, key2=val2,
key3=[
{key4=val4, key5=val5},
{key6=val6, key7=val7}
]
}
I now want to flatten that map, so that all entries are on the same level:
{key1=val1, key2=val2, key4=val4, key5=val5,key6=val6, key7=val7}
When I try
map.values().forEach(map.get("key3")::addAll);
as described in this post, I get the following error:
invalid method reference
cannot find symbol
symbol: method addAll(T)
location: class Object
where T is a type-variable:
T extends Object declared in interface Iterable
Is there any generic way to flatten any given Map?
Not sure if I understood the question correctly, but something like this might work.
Haven't checked all the syntax yet, so there might be some mistake somewhere.
Stream<Map.Entry<String, String>> flatten(Map<String, Object> map) {
return map.entrySet()
.stream()
.flatMap(this::extractValue);
}
Stream<Map.Entry<String, String>> extractValue(Map.Entry<String, Object> entry) {
if (entry.getValue() instanceof String) {
return Stream.of(new AbstractMap.SimpleEntry(entry.getKey(), (String) entry.getValue()));
} else if (entry.getValue() instanceof Map) {
return flatten((Map<String, Object>) entry.getValue());
}
}
Then you could do:
Map<String, String> flattenedMap = flatten(yourmap)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
You can make use of a recursive helper method:
static void forEachValue(Map<String, Object> source, BiConsumer<? super String, ? super Object> action) {
for (final Map.Entry<String, Object> entry : source.entrySet()) {
if (entry.getValue() instanceof Map) {
forEachValue((Map<String, Object>) entry.getValue(), action);
} else {
action.accept(entry.getKey(), entry.getValue());
}
}
}
Which then can be called like this:
Map<String, Object> map = ...;
Map<String, Object> flattened = new HashMap<>();
forEachValue(map, map::put);
I've used this approach with the BiConsumer to not limit the method to only flatten the nested map into another map, but the caller may decide himself what he wants to do with every key-value pair.
You should try this:
Map<String, Object> flatenedMap = new HashMap<>();
map.forEach((key, value) -> {
if(value instanceof Map) {
flatenedMap.putAll((Map) value);
} else {
flatenedMap.put(key, value);
}
});
If you have more than one level of nesting you can use recursive alg.
static Map<String, Object> flatMap(Map<String, Object> map) {
Map<String, Object> flatenedMap = new HashMap<>();
map.forEach((key, value) -> {
if(value instanceof Map) {
flatenedMap.putAll(flatMap((Map) value));
} else {
flatenedMap.put(key, value);
}
});
return flatenedMap;
}
I have a List of objects. Some objects are Map<String, String> and others are Map<String, List<String>> types. I need to group those in to different lists.
Please tell me If there any methods to handle these challenge.
This looked like a fun code challenge. I wrote a small java class demonstrating how you can use 'instanceof' operator to split out these values into separate collections.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Scratch {
public static void main(String[] args) {
// test data
List<Map<String, ?>> mixed = new ArrayList<>();
Map<String, String> strings = new HashMap<>();
strings.put("x", "y");
Map<String, List<String>> lists = new HashMap<>();
List<String> list = new ArrayList<>();
list.add("z");
lists.put("w", list);
mixed.add(strings);
mixed.add(lists);
// split out data
Map<String, String> onlyStrings = new HashMap<>();
Map<String, List<String>> onlyLists = new HashMap<>();
for (Map<String, ?> item : mixed) {
for (Map.Entry<String, ?> entry : item.entrySet()) {
Object value = entry.getValue();
if (value instanceof String) {
onlyStrings.put(entry.getKey(), (String)entry.getValue());
} else if (value instanceof List) {
onlyLists.put(entry.getKey(), (List<String>)entry.getValue());
}
}
}
// print out
System.out.println("---Strings---");
for (Map.Entry<String, String> entry : onlyStrings.entrySet()) {
System.out.println(entry);
}
System.out.println("---Lists---");
for (Map.Entry<String, List<String>> entry : onlyLists.entrySet()) {
System.out.println(entry);
}
}
}
Output
---Strings---
x=y
---Lists---
w=[z]
Hope it helps and is what you are after
I have a HashMap in Java, the contents of which (as you all probably know) can be accessed by
HashMap.get("keyname");
If a have a HashMap inside another HashMap i.e. a nested HashMap, how would i access the contents? Can i do this like this, inline:
HashMap.get("keyname").get("nestedkeyname");
Thank you.
You can do it like you assumed. But your HashMap has to be templated:
Map<String, Map<String, String>> map =
new HashMap<String, Map<String, String>>();
Otherwise you have to do a cast to Map after you retrieve the second map from the first.
Map map = new HashMap();
((Map)map.get( "keyname" )).get( "nestedkeyname" );
You can get the nested value by repeating .get(), but with deeply nested maps you have to do a lot of casting into Map. An easier way is to use a generic method for getting a nested value.
Implementation
public static <T> T getNestedValue(Map map, String... keys) {
Object value = map;
for (String key : keys) {
value = ((Map) value).get(key);
}
return (T) value;
}
Usage
// Map contents with string and even a list:
{
"data": {
"vehicles": {
"list": [
{
"registration": {
"owner": {
"id": "3643619"
}
}
}
]
}
}
}
List<Map> list = getNestedValue(mapContents, "data", "vehicles", "list");
Map first = list.get(0);
String id = getNestedValue(first, "registration", "owner", "id");
Yes.
See:
public static void main(String args[]) {
HashMap<String, HashMap<String, Object>> map = new HashMap<String, HashMap<String,Object>>();
map.put("key", new HashMap<String, Object>());
map.get("key").put("key2", "val2");
System.out.println(map.get("key").get("key2"));
}
If you plan on constructing HashMaps with variable depth, use a recursive data structure.
Below is an implementation providing a sample interface:
class NestedMap<K, V> {
private final HashMap<K, NestedMap> child;
private V value;
public NestedMap() {
child = new HashMap<>();
value = null;
}
public boolean hasChild(K k) {
return this.child.containsKey(k);
}
public NestedMap<K, V> getChild(K k) {
return this.child.get(k);
}
public void makeChild(K k) {
this.child.put(k, new NestedMap());
}
public V getValue() {
return value;
}
public void setValue(V v) {
value = v;
}
}
and example usage:
class NestedMapIllustration {
public static void main(String[] args) {
NestedMap<Character, String> m = new NestedMap<>();
m.makeChild('f');
m.getChild('f').makeChild('o');
m.getChild('f').getChild('o').makeChild('o');
m.getChild('f').getChild('o').getChild('o').setValue("bar");
System.out.println(
"nested element at 'f' -> 'o' -> 'o' is " +
m.getChild('f').getChild('o').getChild('o').getValue());
}
}
As others have said you can do this but you should define the map with generics like so:
Map<String, Map<String, String>> map = new HashMap<String, Map<String,String>>();
However, if you just blindly run the following:
map.get("keyname").get("nestedkeyname");
you will get a null pointer exception whenever keyname is not in the map and your program will crash. You really should add the following check:
String valueFromMap = null;
if(map.containsKey("keyname")){
valueFromMap = map.get("keyname").get("nestedkeyname");
}
Yes, if you use the proper generic type signature for the outer hashmap.
HashMap<String, HashMap<String, Foo>> hm = new HashMap<String, HashMap<String, Foobar>>();
// populate the map
hm.get("keyname").get("nestedkeyname");
If you're not using generics, you'd have to do a cast to convert the object retrieved from the outer hash map to a HashMap (or at least a Map) before you could call its get() method. But you should be using generics ;-)
I prefer creating a custom map that extends HashMap. Then just override get() to add extra logic so that if the map doesnt contain your key. It will a create a new instance of the nested map, add it, then return it.
public class KMap<K, V> extends HashMap<K, V> {
public KMap() {
super();
}
#Override
public V get(Object key) {
if (this.containsKey(key)) {
return super.get(key);
} else {
Map<K, V> value = new KMap<K, V>();
super.put((K)key, (V)value);
return (V)value;
}
}
}
Now you can use it like so:
Map<Integer, Map<Integer, Map<String, Object>>> nestedMap = new KMap<Integer, Map<Integer, Map<String, Object>>>();
Map<String, Object> map = (Map<String, Object>) nestedMap.get(1).get(2);
Object obj= new Object();
map.put(someKey, obj);
I came to this StackOverflow page looking for a something ala valueForKeyPath known from objc. I also came by another post - "Key-Value Coding" for Java, but ended up writing my own.
I'm still looking for at better solution than PropertyUtils.getProperty in apache's beanutils library.
Usage
Map<String, Object> json = ...
public String getOptionalFirstName() {
return MyCode.getString(json, "contact", "firstName");
}
Implementation
public static String getString(Object object, String key0, String key1) {
if (key0 == null) {
return null;
}
if (key1 == null) {
return null;
}
if (object instanceof Map == false) {
return null;
}
#SuppressWarnings("unchecked")
Map<Object, Object> map = (Map<Object, Object>)object;
Object object1 = map.get(key0);
if (object1 instanceof Map == false) {
return null;
}
#SuppressWarnings("unchecked")
Map<Object, Object> map1 = (Map<Object, Object>)object1;
Object valueObject = map1.get(key1);
if (valueObject instanceof String == false) {
return null;
}
return (String)valueObject;
}
import java.util.*;
public class MyFirstJava {
public static void main(String[] args)
{
Animal dog = new Animal();
dog.Info("Dog","Breezi","Lab","Chicken liver");
dog.Getname();
Animal dog2= new Animal();
dog2.Info("Dog", "pumpkin", "POM", "Pedigree");
dog2.Getname();
HashMap<String, HashMap<String, Object>> dogs = new HashMap<>();
dogs.put("dog1", new HashMap<>() {{put("Name",dog.name);
put("Food",dog.food);put("Age",3);}});
dogs.put("dog2", new HashMap<>() {{put("Name",dog2.name);
put("Food",dog2.food);put("Age",6);}});
//dogs.get("dog1");
System.out.print(dogs + "\n");
System.out.print(dogs.get("dog1").get("Age"));
}
}
Example Map:
{
"data": {
"userData": {
"location": {
"city": "Banja Luka"
}
}
}
}
Implementation:
public static Object getValueFromMap(final Map<String, Object> map, final String key) {
try {
final String[] tmpKeys = key.split("\\.");
Map<String, Object> currentMap = map;
for (int i = 0; i < tmpKeys.length - 1; i++) {
currentMap = (Map<String, Object>) currentMap.get(tmpKeys[i]);
}
return currentMap.get(tmpKeys[tmpKeys.length - 1]);
} catch (Exception exception) {
return null;
}
}
Usage:
final Map<String, Object> data = new HashMap<>();
final Map<String, Object> userData = new HashMap<>();
final Map<String, Object> location = new HashMap<>();
location.put("city", "Banja Luka");
userData.put("location", location);
data.put("userData", userData);
System.out.println(getValueFromMap(data, "userData.location.city"));
Result:
Banja Luka
Process finished with exit code 0
I hit this discussion while trying to figure out how to get a value from a nested map of unknown depth and it helped me come up with the following solution to my problem. It is overkill for the original question but maybe it will be helpful to someone that finds themselves in a situation where you have less knowledge about the map being searched.
private static Object pullNestedVal(
Map<Object, Object> vmap,
Object ... keys) {
if ((keys.length == 0) || (vmap.size() == 0)) {
return null;
} else if (keys.length == 1) {
return vmap.get(keys[0]);
}
Object stageObj = vmap.get(keys[0]);
if (stageObj instanceof Map) {
Map<Object, Object> smap = (Map<Object, Object>) stageObj;
Object[] skeys = Arrays.copyOfRange(keys, 1, keys.length);
return pullNestedVal(smap, skeys);
} else {
return null;
}
}