84

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);
Shreyos Adikari
  • 12,348
  • 19
  • 73
  • 82
Mawia
  • 4,220
  • 13
  • 40
  • 55

12 Answers12

74

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()));
skeryl
  • 5,225
  • 4
  • 26
  • 28
  • 2
    With some extra care: Map newMap = map.entrySet().stream() .filter(entry -> entry.getValue() instanceof String) .collect(Collectors.toMap(Map.Entry::getKey, e -> (String)e.getValue())); – Balázs Németh Apr 20 '16 at 13:08
  • 2
    why not `e.getValue().toString()` why explicit cast? – Anton Balaniuc May 31 '18 at 10:48
  • 1
    @AntonBalaniuc we're assuming that the OP *knows* that the values are all Strings so here we're using a more strict cast instead of coercing a String value from the object. Of course, as mentioned in the answer, if there's some other mechanism more appropriate for creating a string from the value Objects, that should be used instead of a cast. – skeryl May 31 '18 at 20:58
  • 2
    @AntonBalaniuc old question, but calling `toString()` can be dangerouse... Let me explain. If for any reason the object you want to convert to String is not actually a String you are expecting generalized as an object, but something else, the `toString()` method might give you tokenized value of that object, thus introducing hard to detect bug in your code. Casting will at least throw the `RuntimeException` in such case. – Przemysław Gęsieniec Dec 15 '21 at 09:34
35

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().

Shreyos Adikari
  • 12,348
  • 19
  • 73
  • 82
26

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.

Mikhail
  • 4,175
  • 15
  • 31
7

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);
cambecc
  • 4,083
  • 1
  • 23
  • 24
4

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()));
BJ Dela Cruz
  • 5,194
  • 13
  • 51
  • 84
3

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

Suresh Atta
  • 120,458
  • 37
  • 198
  • 307
3

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");
        }
    }
selig
  • 4,834
  • 1
  • 20
  • 37
2

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

yunspace
  • 2,532
  • 20
  • 20
  • 1
    TransformedMap and MapUtils in Apache Common Collections http://commons.apache.org/proper/commons-collections/ – arulraj.net Sep 02 '14 at 14:09
2
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;
}
  • 2
    While adding code sure does solve the problem, it'd be great to add some explanation to the code so future visitors and people new to the language can understand and learn from it better. and Welcome to Stack Overflow! – DeadChex Jun 18 '15 at 15:22
2

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()));
Johnny
  • 14,397
  • 15
  • 77
  • 118
2

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());
jiangke
  • 305
  • 3
  • 13
1

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.

Glen Best
  • 22,769
  • 3
  • 58
  • 74