Convert Map<String,Object> to Map<String,String>

How can I convert Map<String,Object> to Map<String,String> ?

This does not work:

Map<String,Object> map = new HashMap<String,Object>(); //Object is containing String
Map<String,String> newMap =new HashMap<String,String>(map);
154244 次浏览

If your Objects are containing of Strings only, then you can do it like this:

Map<String,Object> map = new HashMap<String,Object>(); //Object is containing String
Map<String,String> newMap =new HashMap<String,String>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if(entry.getValue() instanceof String){
newMap.put(entry.getKey(), (String) entry.getValue());
}
}

If every Objects are not String then you can replace (String) entry.getValue() into entry.getValue().toString().

Not possible.

This a little counter-intuitive.

You're encountering the "Apple is-a fruit" but "Every Fruit is not an Apple"

Go for creating a new map and checking with instance of with String

As you are casting from Object to String I recommend you catch and report (in some way, here I just print a message, which is generally bad) the exception.

    Map<String,Object> map = new HashMap<String,Object>(); //Object is containing String
Map<String,String> newMap =new HashMap<String,String>();


for (Map.Entry<String, Object> entry : map.entrySet()) {
try{
newMap.put(entry.getKey(), (String) entry.getValue());
}
catch(ClassCastException e){
System.out.println("ERROR: "+entry.getKey()+" -> "+entry.getValue()+
" not added, as "+entry.getValue()+" is not a String");
}
}

There are two ways to do this. One is very simple but unsafe:

Map<String, Object> map = new HashMap<String, Object>();
Map<String, String> newMap = new HashMap<String, String>((Map)map);  // unchecked warning

The other way has no compiler warnings and ensures type safety at runtime, which is more robust. (After all, you can't guarantee the original map contains only String values, otherwise why wouldn't it be Map<String, String> in the first place?)

Map<String, Object> map = new HashMap<String, Object>();
Map<String, String> newMap = new HashMap<String, String>();
@SuppressWarnings("unchecked") Map<String, Object> intermediate =
(Map)Collections.checkedMap(newMap, String.class, String.class);
intermediate.putAll(map);

Generic types is a compile time abstraction. At runtime all maps will have the same type Map<Object, Object>. So if you are sure that values are strings, you can cheat on java compiler:

Map<String, Object> m1 = new HashMap<String, Object>();
Map<String, String> m2 = (Map) m1;

Copying keys and values from one collection to another is redundant. But this approach is still not good, because it violates generics type safety. May be you should reconsider your code to avoid such things.

While you can do this with brute casting and suppressed warnings

Map<String,Object> map = new HashMap<String,Object>();
// Two casts in a row.  Note no "new"!
@SuppressWarnings("unchecked")
Map<String,String> newMap = (HashMap<String,String>)(Map)map;

that's really missing the whole point. :)

An attempt to convert a narrow generic type to a broader generic type means you're using the wrong type in the first place.

As an analogy: Imagine you have a program that does volumous text processing. Imagine that you do first half of the processing using Objects (!!) and then decide to do the second half with correct-typing as a String, so you narrow-cast from Object to String. Fortunately, you can do this is java (easily in this case) - but it's just masking the fact you're using weak-typing in the first half. Bad practice, no argument.

No difference here (just harder to cast). You should always use strong typing. At minimum use some base type - then generics wildcards can be used ("? extends BaseType" or "? super BaseType") to give type-compatability and automatic casting. Even better, use the correct known type. Never use Object unless you have 100% generalised code that can really be used with any type.

Hope that helps! :) :)


Note: The generic strong typing and type-casting will only exist in .java code. After compilation to .class we are left with raw types (Map and HashMap) with no generic type parameters plus automatic type casting of keys and values. But it greatly helps because the .java code itself is strongly-typed and concise.

The following will transform your existing entries.

TransformedMap.decorateTransform(params, keyTransformer, valueTransformer)

Where as

MapUtils.transformedMap(java.util.Map map, keyTransformer, valueTransformer)

only transforms new entries into your map

private Map<String, String> convertAttributes(final Map<String, Object> attributes) {
final Map<String, String> result = new HashMap<String, String>();
for (final Map.Entry<String, Object> entry : attributes.entrySet()) {
result.put(entry.getKey(), String.valueOf(entry.getValue()));
}
return result;
}

Now that we have Java 8/streams, we can add one more possible answer to the list:

Assuming that each of the values actually are String objects, the cast to String should be safe. Otherwise some other mechanism for mapping the Objects to Strings may be used.

Map<String,Object> map = new HashMap<>();
Map<String,String> newMap = map.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> (String)e.getValue()));

Great solutions here, just one more option that taking into consideration handling of null values:

Map<String,Object> map = new HashMap<>();


Map<String,String> stringifiedMap = map.entrySet().stream()
.filter(m -> m.getKey() != null && m.getValue() !=null)
.collect(Collectors.toMap(Map.Entry::getKey, e -> (String)e.getValue()));

Use the Java 8 way of converting a Map<String, Object> to Map<String, String>. This solution handles null values.

Map<String, String> keysValuesStrings = keysValues.entrySet().stream()
.filter(entry -> entry.getValue() != null)
.collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue().toString()));

I find the easiest way by use guava:

    <dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>

then you can do this:

Map<String,Object> map = new HashMap<String,Object>();
Map<String,String> newMap = Maps.transformValues(map, Functions.toStringFunction());