Easily convert Map<String, Object> to Map<String, String>

10,680

Solution 1

Well you can't safely cast it to a Map<String, String> because even though you know you've only got strings as the values, the compiler doesn't. That's like expecting:

Object x = "foo";
String y = x;

to work - it doesn't; you need to explicitly cast.

Likewise you can explicitly cast here, too, if you go via Object:

Map<String, Object> x = ...;
Map<String, String> y = (Map<String, String>) (Object) x;

Now you'll get a warning saying that it's an unchecked cast, because unlike the earlier "object to string" cast, there's no execution-time check that it's really valid. Type erasure means that a map doesn't really know its key/value types. So you end up with checking only being done when elements are fetched:

import java.util.*;

class Test {
    public static void main(String[] args) {
        Map<String, Object> x = new HashMap<>();
        x.put("foo", "bar");
        x.put("number", 0);
        Map<String, String> y = (Map<String, String>) (Object) x;
        // This is fine
        System.out.println(y.get("foo"));

        // This goes bang! It's trying to cast an Integer to a String
        System.out.println(y.get("number"));
    }
}

So if you really want to avoid creating a new map, this "cast via Object" will work - but it's far from ideal.

Your approach is safer, although you can make it slightly more efficient by avoiding the lookup:

public static Map<String, String> copyToStringValueMap(
        Map<String, Object> input) {
    Map<String, String> ret = new HashMap<>();
    for (Map.Entry<String, Object> entry : input.entrySet()) {
        ret.put(entry.getKey(), (String) entry.getValue());
    }
    return ret;
}

Solution 2

A Java 8 solution:

private Map<String, String> stringifyValues(Map<String, Object> variables) {
    return variables.entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey, e -> (String) e.getValue()));
}

Solution 3

Good solutions here, but I want to add another one that taking into consideration handling 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()));
Share:
10,680
jobukkit
Author by

jobukkit

Updated on July 24, 2022

Comments

  • jobukkit
    jobukkit almost 2 years

    An API I am using has a method that returns a Map<String, Object>, but I know the Object's are String's in this case, so I want it as a Map<String, String>.

    But for some reason I can't just cast it, Java says Map<String, Object> cannot be casted to Map<String, String>, for some reason.

    I used:

    Map<String, Object> tempMap = someApiMethodReturningAMap();
    Map<String, String> map = new HashMap<String, String>();
    for (String i : tempMap.keySet()) {
        map.put(i, String.valueOf(tempMap.get(i)));
    }
    

    as a workaround, but is there an easier way?