1

As a Salesforce developer, I am frequently working with Maps and am looking to abstract the process of generating maps from SObject lists.

I've created the following helper class:

private class MapBySharedKeyExtractor {
    SObjectField keyField;
    XAP_PRED_SObjectPredicateIntf predicate;

    MapBySharedKeyExtractor(SObjectField keyField, XAP_PRED_SObjectPredicateIntf predicate) {
        this.keyField = keyField;
        this.predicate = predicate;
    }

    public Map<Object, List<SObject>> extractFrom(List<SObject> homogeneousSObjectList) {
        if (homogeneousSObjectList == null || homogeneousSObjectList.isEmpty()) {
            return null;
        }

        Map<Object, List<SObject>> sObjectListByKeyMap = new Map<Object, List<SObject>>();
        for (SObject sObj : homogeneousSObjectList) {
            if (this.predicate.isTrueFor(sObj)) {
                Object key = sObj.get(this.keyField);
                if (!sObjectListByKeyMap.containsKey(key)) {
                    sObjectListByKeyMap.put(key, new List<SObject>());
                }
                sObjectListByKeyMap.get(key).add(sObj);
            }
        }
        return sObjectListByKeyMap;
    }
}

This is the prediate interface:

public interface XAP_PRED_SObjectPredicateIntf {
    Boolean isTrueFor(SObject sObj);
}

However, as the final step of this journey, I would like to be able to change the generated Map into concrete types, so I would like (for example) to have a helper method:

  public static Map<Id, List<SObject>> mapBySpecifiedIdField(List<SObject> homogeneousSObjectList, SObjectField idField) {
        Map<Object, List<SObject>> sObjectListBySpecifiedObjectMap
                = new MapBySharedKeyExtractor(idField, new XAP_PRED_SObjectFieldHasNonNullValue(idField))
                .extractFrom(homogeneousSObjectList);

        String mapType = 'Map<Id, List<' + homogeneousSObjectList[0].getSObjectType() + '>>';
        Map<Id, List<SObject>> concreteSObjectListByIdMap = (Map<Id, List<SObject>>) Type.forName(mapType).newInstance();
        concreteSObjectListByIdMap.putAll(sObjectListBySpecifiedObjectMap);
        return concreteSObjectListByIdMap;
    }

However this fails because this is invalid:

 concreteSObjectListByIdMap.putAll(sObjectListBySpecifiedObjectMap);

Is there any way I can make this work without iterating through sObjectListBySpecifiedObjectMap and casting each key individually?

  • @Sebastian Kessel, how did you manage to edit my post only 24 seconds after I asked it!? :-) – Brian Kessler May 1 at 15:42
  • That's how fast I am. :D – Sebastian Kessel May 1 at 15:55
  • this might be relevant GitHub SObjectIndex to allow indexing lists of sobjects by any sobjectField – cropredy May 1 at 17:24
  • @cropredy, care to be more specific with how this helps with the above? It looks like it strives to achieve a similar goal, but (1) without filtering by predicate and (2) forcing the consumer to live without type safety during the life of the SObjectIndex (since SFDC doesn't have parameterized types) – Brian Kessler May 1 at 17:33
  • Brian -- more in the way of an alternative approach to pivoting sobjects by values; not necessarily as strictly addressing your OP – cropredy May 1 at 18:17
5

No. You can't convert key types without a loop. The Map structure is not flexible in this way. The List type is a bit more flexible, however, and so you can make this minor simplification, as concretely typing the List<SObject> offers few tangible benefits unless you plan to upsert it.

Map<Id, List<SObject>> concreteSObjectListByIdMap = new Map<Id, List<SObject>>();

No need for dynamic Type.forName instantiation.

0

This isn't really the answer I am looking for because it is computationally expensive, but this will work without a loop:

public Map<Id, List<SObject>> mapBySpecifiedIdField(List<SObject> sObjectList, SObjectField idField) {
    Map<Object, List<SObject>> sObjectListBySpecifiedObjectMap
            = new MapExtractor(idField, new XAP_PRED_SObjectFieldHasNonNullValue(idField))
                    .extractFrom(sObjectList);
    return (Map<Id, List<SObject>>) JSON.deserialize(
            JSON.serialize(sObjectListBySpecifiedObjectMap), Map<Id, List<SObject>>.class
    );
}

While this could be useful in contexts where the Map needs to be passed to another function where the signature is important, it is worth noting:

  1. The serialize/deserialize process will break all object references. So, this will not be as useful as it might otherwise seem in a Trigger context (for example).

  2. If it is not necessary to pass the Map or keySet() onto another method, it may not be necessary to fix key Type, as get() will work as expected.

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.