6

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?

1
  • 3
    That's easy and understandable way, I would only recommend to change the cast to 'String.valueOf()' - you need to have a meaningful impl of the toString() method of the Object arg. Commented May 18, 2014 at 11:26

3 Answers 3

12

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;
}
Sign up to request clarification or add additional context in comments.

Comments

8

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

Comments

2

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.