What is the best way to cast/convert Map<String, SObject> to Map<Object, Object> and back again?
Looking in to this question, I realized that the type system is inherently broken. They have "fixed" Maps, it seems, but in a way that no longer lets you mix and match some types, especially Object.
The list class is still broken, however:
class c1 {
}
class c2 extends c1 {
}
c1[] s = new c2[0];
Generally speaking, unless/until they fix the problem, you won't be able to be polymorphic this way; your classes that intend to call this class would also need to use Map<Object, Object>
, which will involve some casting.
One thought I had would be to create an interface like this:
public interface keyValueMap {
Set<Object> getKeys();
Object[] getValues();
Object getKey(Object value);
Object setKey(Object key, Object value);
}
And then create specific wrappers:
public class StringSObjectMap implements KeyValueMap {
Map<String, SObject> values;
public StringSObjectMap(Map<String, SObject> values) {
this.values = values;
}
public StringSObjectMap() {
values = new Map<String, SObject>();
}
public Set<Object> getKeys() {
Set<Object> results = new Set<Object>();
for(String value: values.keySet()) {
results.add(value);
}
return results;
}
public Object[] getValues() {
return (Object[])values.values();
}
public Object getKey(Object value) {
return (Object)values.get((String)value);
}
public Object setKey(Object key, Object value) {
return values.put((String)key, (SObject)value);
}
}
Note that since we can't cast Set<SObject>
to Set<Object>
, we have to build a duplicate key set. Make sure you cache the results for performance reasons.
Finally, you can write your method to use the new interface:
public keyValueMap doSomething(keyValueMap inputMap) {
And to call it, you'd pass in the appropriate sub-wrapper:
keyValueMap results = doSomething(new StringSObjectMap(recordMap));
Depending on what you're doing, this may require additional post-processing to get the values back out.
You might also just leave the return type as Map<Object, Object>
but you'll end up having to process the keys again, as demonstrated in the wrapper above. One additional step might help you here; you can pass in the type of return value you want:
public keyValueMap doSomething(keyValueMap inputMap, Type mapType) {
keyValueMap results = (keyValueMap)mapType.newInstance();
Called as:
keyValueMap results = doSomething(new StringSObjectMap(recordMap), StringSObjectMap.class);
You could also create a custom Iterator, but this doesn't work in for-each loops (but you can use iter.next/iter.hasnext):
public class KeyValue {
public Object key;
public Object value;
public KeyValue(Object k, Object v) {
key = k;
value = v;
}
}
public class StringSObjectIter implements Iterable<KeyValue>, Iterator<KeyValue> {
Map<String, SObject> values;
String[] keys;
public StringSObjectIter(Map<String, SObject> values) {
this.values = values;
}
StringSObjectIter(StringSObjectIter copy) {
values = copy.values;
keys = new List<String>(values.keySet());
}
public Boolean hasNext() {
return !keys.isEmpty();
}
public KeyValue next() {
String k = keys.remove(0);
return new KeyValue(k, values.get(k));
}
public Iterator<KeyValue> iterator() {
return new StringSObjectIter(this);
}
}
Which you can use in your method as:
public keyValueMap doSomething(Iterable<KeyValue> inputs) {
Iterator<KeyValue> iter = inputs.iterator();
while(iter.hasNext()) {
KeyValue value = iter.next();
...
Calling method for this:
keyValueMap results = doSomething(new StringSObjectIter(recordMap));
No, there isn't a better way. With Set
, you can hack around it to some extent. But with a Map
, you care about the values, not just the keys, so you can't do it without looping.
Set<String> values = new Set<String>();
Set<Object> generic = new Set<Object>((List<Object>)new List<String>(values));
It's not immediately clear if this song and dance is even faster than looping anyway.