Dynamically cast to a proper subclass from parent class in a method - java

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;
}
}

Related

How to convert nested Map to List of POJO using Mapstruct

How to convert Map<String, Map<String, Long>> to List using MapStruct?
source:
Map<String, Map<String, Long>>
target:
List<DTO1>
DTO1:
private String name;
private List<DTO2> dto2List;
DTO2:
private String type;
private Long count;
Doing such a mapping is possible with some custom methods. I assume that the map entry set needs to be mapped to the list.
e.g.
#Mapper
public abstract class MyMapper {
public List<DTO1> map(Map<String, Map<String, Long>> source) {
if ( source == null ) {
return null;
}
return toDto1List( source.entrySet() );
}
protected abstract List<DTO1> toDto1List(Collection<Map.Entry<String, Map<String, Long>>> collection);
protected abstract List<DTO2> toDto2List(Collection<Map.Entry<String, Long>> collection);
protected DTO1 entryToDto1(Map.Entry<String, Map<String, Long>> entry) {
if ( entry == null ) {
return null;
}
return new DTO1( entry.getKey(), toDto2List( entry.getValue().entrySet() ) );
}
protected DTO2 entryToDto2(Map.Entry<String, Long> entry) {
if ( entry == null ) {
return null;
}
return new DTO2( entry.getKey(), entry.getValue() );
}
}
map.entrySet().stream().map(entry -> new DTO1(...)).collect(Collectors.toList())

different setter method for HashMap<>

I have a Class like this:
public class MyClass
{
private int id;
private Map<String, String> myMap;
public Map<String, String> getMyMap()
{
return myMap;
}
public void setMyMap(Map<String, String> myMap)
{
this.myMap = myMap;
}
}
I added new setter method(overloading) because i didn't want to do set HashMap directly, and that's what you see now :
public class MyClass
{
private int id;
private Map<String, String> myMap;
public Map<String, String> getMyMap()
{
return myMap;
}
public void setMyMap(Map<String, String> myMap)
{
this.myMap = myMap;
}
public void setMyMap(String key , String value)
{
setMyMap(new HashMap<>(){{put(key, value);}});
}
}
But because i used new HashMap<>(){{put(key, value);}} keyword every time i use this method , it create new Map and last items deleted .
So i have 2 question:
1-correct solution for set items by 2nd setter method
2-how i could use this setter method for multiple put's for this situations:
MyClass.setMyMap(new HashMap<>()
{{
put("title", title);
put("id", id);
}});
Thank you guys for your time .
It depends on what your class does. But in general, I would not expose a setter for a map field.
It makes sense to add a constructor with a map argument, then do something like this:
public class MyClass
{
private final int id;
private final Map<String, String> myMap;
public MyClass(int id, Map<String, String> myMap) {
this.id = id;
this.myMap = myMap;
}
public Map<String, String> getMyMap()
{
return myMap;
}
public void addPairs(Map<String, String> pairs)
{
myMap.putAll(pairs);
}
public void addPair(String key, String value)
{
myMap.put(key, value);
}
}
Of course, you can expose an additional constructor:
public MyClass(int id) {
this.id = id;
this.myMap = new HashMap<>();
}
Try some thing like this:
public void setMyMap(String key , String value) {
if(myMap == null)
myMap = new HashMap<String, String>();
myMap.put(key, value);
}
You've already declared class field myMap and you want to use it in setMyMap method.
Do null check. If the field is null then create a new map. Then use put method to store data in the map.

Spring load Map value from yml file

I have a Spring Boot application with the following application.yml
Detail_1:
name: X,Y,Z
place: xplace,yplace,zplace
Detail_2:
name: X,Y,Z
place: xplaceanother,yplaceanother,zplaceanother
How can I obtain this map in java:
X {
detail1 :xplace
detail2 :xplaceanother
}
Y {
detail1:yplace,
detail2:yplaceanother
}
Z{
detail1:zplace,
detail2:zplaceanother
}
I have tried the following code :
#Value${detail1.name}
private String names;
#value${detail2.place}
List<Object> Names = Arrays.asList(getNames().split(","));
List<Object> places = Arrays.asList(getPlaces().split(","));
Then I tried to create a map of names and places corresponding to detail 1
similarly I fetched names and places for detail 2
But In this case i end up with 2 maps , one for detail 1 and one for detail 2.
I need to create a single map.
You need to use #ConfigurationProperties annotation
The following URLs provide good examples in both .properties and .yml format:
https://www.mkyong.com/spring-boot/spring-boot-configurationproperties-example/
https://www.baeldung.com/configuration-properties-in-spring-boot
Please update your config like below in application.yml
map:
detail1:
name:X,Y,Z
place:xplace,yplace,zplace
detail2:
name:X,Y,Z
place:xplaceanother,yplaceanother,zplaceanother
and then configure the property as below,
DetailConfig.java
#Component
#ConfigurationProperties(prefix="map")
public class DetailConfig {
private Map<String, Object> detail1;
private Map<String, Object> detail2;
public Map<String, Object> getDetail1() {
return detail1;
}
public void setDetail1(Map<String, Object> detail1) {
this.detail1 = detail1;
}
public Map<String, Object> getDetail2() {
return detail2;
}
public void setDetail2(Map<String, Object> detail2) {
this.detail2 = detail2;
}
}
You can use the following pojo for property;
public class Detail {
private List<String> name;
private List<String> place;
public Map<String, String> getNamePlaceMap() {
return IntStream.range(0, name.size()).boxed()
.collect(Collectors.toMap(i -> name.get(i), i -> place.get(i)));
}
// getters/setters
}
and use the following configuration to get properties into context;
#Configuration
public class Config {
#Bean
#ConfigurationProperties(prefix = "detail-1")
public Detail detailOne() {
return new Detail();
}
#Bean
#ConfigurationProperties(prefix = "detail-2")
public Detail detailTwo() {
return new Detail();
}
}
and autowire them and pass them to the logic where that map is created;
#Service
public class TestService {
#Autowired
private Detail detailOne;
#Autowired
private Detail detailTwo;
public void test() {
System.out.println(createSpecialMap(detailOne, detailTwo));
}
private static Map<String, Map<String, String>> createSpecialMap(Detail detailOne, Detail detailTwo) {
Map<String, Map<String, String>> resultMap = new HashMap<>();
detailOne.getNamePlaceMap().forEach((key, value) -> {
Map<String, String> subMap = resultMap.getOrDefault(key, new HashMap<>());
subMap.put("detail1", value);
resultMap.put(key, subMap);
});
detailTwo.getNamePlaceMap().forEach((key, value) -> {
Map<String, String> subMap = resultMap.getOrDefault(key, new HashMap<>());
subMap.put("detail2", value);
resultMap.put(key, subMap);
});
return resultMap;
}
}
results in;
{
X={detail1=xplace, detail2=xplaceanother},
Y={detail1=yplace, detail2=yplaceanother},
Z={detail1=zplace, detail2=zplaceanother}
}
Or better in readability, using a Letter class;
public class Letter {
private String name;
private String detail1;
private String detail2;
public Letter(String name, String detail1, String detail2) {
this.name = name;
this.detail1 = detail1;
this.detail2 = detail2;
}
// getters/setters
}
doing the following;
private static List<Letter> createList(Detail detailOne, Detail detailTwo) {
List<Letter> resultList = new ArrayList<>();
Map<String, String> detailOneMap = detailOne.getNamePlaceMap();
Map<String, String> detailTwoMap = detailTwo.getNamePlaceMap();
Set<String> keySet = new HashSet<>();
keySet.addAll(detailOneMap.keySet());
keySet.addAll(detailTwoMap.keySet());
return keySet.stream()
.map(key -> new Letter(key, detailOneMap.get(key), detailTwoMap.get(key)))
.collect(Collectors.toList());
}
will result in;
[
Letter{name='X', detail1='xplace', detail2='xplaceanother'},
Letter{name='Y', detail1='yplace', detail2='yplaceanother'},
Letter{name='Z', detail1='zplace', detail2='zplaceanother'}
]
which is a better result than a raw map of map...

How to assert Map contains Map with entry

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));

How do I access nested HashMaps in Java?

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;
}
}

Categories

Resources