0

I have a method:

public Map<Object, Object> swap(Map<Object, Object> valueByKeyMap) {
    Map<Object, Object> keyByValueMap = new Map<Object, Object>();
    for (Object key : valueByKeyMap.keySet()) {
        Object value = valueByKeyMap.get(key);
        if (!keyByValueMap.containsKey(value)) {
            keyByValueMap.put(value, key);
        } else {
            throw new XAP_UTIL_MapManyToOneException(
                    'Swap can only be used for maps with one-to-one relationships.  '
                            + ' Conflicting values: '
                            + ' Existing mapping for ' + value + ': ' + keyByValueMap.get(value)
                            + '; new mapping for value: ' + key + '.'
            );
        }
    }
    return keyByValueMap;
}

I'd like to be able to reuse this solution for any Map whose keys are the same type as the value. For example, Map<String, String> or Map<SObjectField, SObjectField>

I thought of doing this:

public Map<String, String> swap(Map<String, String> valueByKeyMap) {
    Map<Object, Object> retypedMap = (Map<Object, Object>) JSON.deserialize(
            JSON.serialize(valueByKeyMap), Map<Object, Object>.class
    );
    Map<Object, Object> swappedMap = this.swap(retypedMap);
    return (Map<String, String>) JSON.deserialize(
            JSON.serialize(swappedMap), Map<String, String>.class
    );
}

But:

  1. Serialize/Deserialize is computationally expensive.
  2. System.JSONException: No mapping for apex type: Object;
  3. System.JSONException: Apex Type unsupported in JSON: Schema.SObjectField

Would there some other way to approach this without creating duplicate code for any required map types?

  • 1
    It's essentially the exact same issue as your previous question. – Adrian Larson May 1 '19 at 19:04
  • Similar, but worse. Here it isn't even possible to use serialize/deserialize to solve (whereas, even though nobody suggested it and I didn't mention it), the previous question is actually solvable with serialize/deserialize if I don't mind the computational expense. Also, here I am more interested in building up a reverse index. It's not so important for me that it is a Map although the type safety would be a plus. – Brian Kessler May 1 '19 at 19:12
1

We don't have Generics, like we do in Java, so you would be stuck writing our own custom Map class in order to implement this algorithm, and perhaps methods to convert to/from this class to actual maps.

| improve this answer | |
  • If I were to sacrifice the type safety of Map for this approach, would there be any to write a reusable conversion method? – Brian Kessler May 1 '19 at 19:13
  • 1
    @BrianKessler regrettably not (mostly). The runtime itself only allows limited conversions. A few generics for sobject/object might work, but you'll only have limited functionality for that effort. – sfdcfox May 1 '19 at 19:52
1

To be honest, this isn't exactly the answer I am looking for. It isn't really a Map and isn't completely interchangeable with a Map. However, this is a generic solution which effectively reverse indexes maps. And the bulk of the code is entirely reusable, although we would need a new constructor to support each Map Type:

public class XAP_UTIL_MapReverseIndexer {
    List<Object> keyObjectList;
    List<Object> valueObjectList;
    public XAP_UTIL_MapReverseIndexer(Map<String, String> valueByKeyMap){
        this.keyObjectList = new List<String>(valueByKeyMap.keySet());
        this.valueObjectList = valueByKeyMap.values();
    }

    public XAP_UTIL_MapReverseIndexer(Map<Double, Double> valueByKeyMap){
        this.keyObjectList = new List<Double>(valueByKeyMap.keySet());
        this.valueObjectList = valueByKeyMap.values();
    }

    public XAP_UTIL_MapReverseIndexer(Map<Integer, Integer> valueByKeyMap){
        this.keyObjectList = new List<Integer>(valueByKeyMap.keySet());
        this.valueObjectList = valueByKeyMap.values();
    }

    public XAP_UTIL_MapReverseIndexer(Map<SObjectField, SObjectField> valueByKeyMap){
        this.keyObjectList = new List<SObjectField>(valueByKeyMap.keySet());
        this.valueObjectList = valueByKeyMap.values();
    }

    private Map<Object, Object> keyByValueMap {
        get{
            if (this.keyByValueMap == null) {
                this.keyByValueMap = this.buildKeyByValueMap();
            }
            return this.keyByValueMap;
        }
        private set;
    }

    public Object get(Object value) {
        return this.keyByValueMap.get(value);
    }

    private Map<Object, Object> buildKeyByValueMap() {
        Map<Object, Object> keyByValueMap = new Map<Object, Object>();
        for (Integer i = 0; i < this.keyObjectList.size(); i++) {
            Object key = this.keyObjectList[i];
            Object value = this.valueObjectList[i];

            if (!keyByValueMap.containsKey(value)) {
                keyByValueMap.put(value, key);
            } else {
                throw new XAP_UTIL_MapManyToOneException(
                        'Swap can only be used for maps with one-to-one relationships.  '
                                + ' Conflicting values: '
                                + ' Existing mapping for ' + value + ': ' + keyByValueMap.get(value)
                                + '; new mapping for value: ' + key + '.'
                );
            }
        }
        return keyByValueMap;
    }

}

Note: I don't know how reliable this solution is.

As of Summer '15, the keySet() and the values() to be predictable (Thanks, @David Reed!), there doesn't seem to be any guarantee these will always correspond.

(For the moment, this seems to work... tested with Strings, Integers, Doubles, SObjectFields...)

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.